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 who is 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 to listen for services you might need, 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 in which the configuration file is read.

A first example

Ready? Let's go for a detailed example. We are going to configure the firewall for a simple 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.

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 the same as 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 carry 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).

Pfouh! 😋

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

pass out on egress

This ultimately gives us:

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. Also take a look at the wiki page dedicated to pf.

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 to start. Do not hesitate to consult the example provided at the end of the document.

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

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

pass in quick on egress proto tcp to port 222

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

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

Convenient commands for pf

I offer you a list 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, the page of which I invite you to read:


Below, I suggest a translation to put this device in place.

It consists of a script which retrieves a list of IPs which will then be ignored by pf.

We start by creating a user dedicated to this script. It's called _pfbadhost. This allows privileges to be separated, which is welcome, although the intermediate steps could be avoided if we did everything as superuser (but not 😋):

# useradd -s / sbin / nologin -d / var / empty _pfbadhost

We download and install the pfbadhost script with the correct permissions:

# ftp
# install -m 755 -o root -g bin / usr / local / bin / pf-badhost

We must then create the files necessary for the proper functioning of the script (list of IPs and logs):

# install -m 640 -o _pfbadhost -g wheel / dev / null /etc/pf-badhost.txt
# install -d -m 755 -o root -g wheel / var / log / pf-badhost
# install -m 640 -o _pfbadhost -g wheel / dev / null /var/log/pf-badhost/pf-badhost.log
# install -m 640 -o _pfbadhost -g wheel / dev / null /var/log/pf-badhost/pf-badhost.log.0.gz

The author recommends installing the ripgrep and mawk programs for better performance. It is optional:

# pkg_add ripgrep mawk

Add these lines to the /etc/doas.conf file so that the _pfbadhost user can reload the list of IPs to blacklist in the firewall:

permit root
permit nopass _pfbadhost cmd / sbin / pfctl args -nf /etc/pf.conf
permit nopass _pfbadhost cmd / sbin / pfctl args -t pfbadhost -T replace -f /etc/pf-badhost.txt
# Optional rule for authlog scanning
permit nopass _pfbadhost cmd / usr / bin / zcat args -f / var / log / authlog /var/log/authlog.0.gz

Make the user _pfbadhost run the script every night at midnight by modifying his crontab:

# crontab -u _pfbadhost -e
~ 0 ~ 1 * * * -s pf-badhost -O openbsd

Thus, pf-badhost will be launched every night between midnight and 1 a.m. This is to prevent all pf-badhost users from using this script at the same time and overloading the servers.

The line is to be written with vi.


Add these lines to the /etc/pf.conf file to make pf take the blacklist into account:

table <pfbadhost> persist file "/etc/pf-badhost.txt"
block quick on egress from <pfbadhost>

Run the script for the first time as if you were the _pfbadhost user:

# doas -u _pfbadhost pf-badhost -O openbsd

Note that you must be able to use doas. Finally, reload the firewall then restart the script:

# pfctl -f /etc/pf.conf
doas -u _pfbadhost pf-badhost -O openbsd

# Configuring pf-badhost

To configure pf-badhost you need to directly edit the script and find what is below the line

### User Configuration Area - START

In particular, you can uncomment (remove the "#") for a specific list or activate IPv6.

Read the manual:


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

If you've chosen strong passphrases, you're normally safe. However, these attacks unnecessarily use 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 identify where these are coming from. attacks to banish the perpetrators. 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 naughty 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, ie 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)

In real time with villain

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. Your feedback will improve it.

The script in question is called villain. You can follow the following link to view the code.

le code

Here is how to install it. First, we download the archive in the / tmp folder:

# cd / tmp
# ftp -o villain.tgz

We decompress the archive:

# tar xvzf naughty.tgz

Then, we proceed to the installation:

# cd naughty-master *
# make install

In order for naughty to work, add a table to the pf configuration in order to retain attackers' IPs. Edit the /etc/pf.conf file to put:

table <naughty_bruteforce> persist
block quick from <naughty_bruteforce>

Reload pf with # pfctl -f /etc/pf.conf then edit the naughty configuration file /etc/vilain.conf. There you will find some examples of the following form:

[name of keeper]
logfile = / path / to / log / to / inspect
regex = regular expression which returns the attacker's IP in case of failure

Other options are available, such as the number of failures before the ban, the IPs to ignore if there is an error ...

In order to run naughty, you need python3. Install the python-3.7. * Package for example.

You can now activate and launch villain like any other service:

# rcctl enable naughty
# rcctl start naughty

If you look at the logs in the / var / log / daemon file, you will see messages there like:

Start naughty for ssh
Start naughty for ssh2
Start naughty for http401
Start naughty for smtp
Start naughty for dovecot

naughty is protecting your server.

You can, if you wish, run this command regularly to remove IPs banned for too long:

pfctl -t vilain_bruteforce -T expires 86400

To view banned IPs, type:

pfctl -t naughty_bruteforce -T show

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. As for naughty, 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 pirates 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 have it, your server is now in safe custody.

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

Pour trouver les ports sur lesquels écoutent les services dont vous pourriez avoir besoin, regardez le contenu du fichier /etc/services. Ainsi, écrire www ou 80 revient au même pour pf, mais est plus lisible pour les humains.

Les règles que vous allez définir seront appliquées dans l'ordre de lecture du fichier de configuration.

Un premier exemple

Prêts ? C'est parti pour un exemple détaillé. Nous allons configurer le parefeu pour un simple site web avec accès sur les ports 80 (www) et 443 (https).

On commence par tout bloquer, et enregistrer dans le journal les connexions interdites avec le mot log.

block log

Afin de nous simplifier la vie, nous allons mettre les ports à ouvrir dans une variable. Ça sera particulièrement pratique lorsqu'on aura davantage de ports à gérer.

tcp_pass = "{ 80 443 }"

Remarquez que c'est identique à tcp_pass = "{ www https }"

Ensuite, on autorise les visiteurs éventuels à accéder à votre serveur. Ce seront des connexions dites "entrantes", on utilisera donc le mot clé "in". Au lieu de chercher le nom de votre interface réseau (qui dépend de votre matériel), nous allons préciser le nom du groupe que portent ces interfaces afin que ça fonctionne sans inquiétudes : egress.

pass in quick on egress proto tcp to port $tcp_pass 
T'es mignon hein, mais c'est du charabia tout ça!

D'accord, nous allons expliquer ce que veut dire cette syntaxe. On peut la traduire la ligne de configuration précédente ainsi : "laisse passer vers l'intérieur (pass in) sans tenir compte des règles qui pourraient suivre (quick) à travers l'interface egress (on egress) pour le protocole tcp (proto tcp) vers les ports dans $tcp_pass (to port $tcp_pass).

Pfouh! 😋

Enfin, on autorise tout le trafic en sortie (mot clé "out") :

pass out on egress 

Cela nous donne au final :

tcp_pass = "{ 80 443 }"
block log

pass in quick on egress proto tcp port $tcp_pass 
pass out on egress all

Facile, non ? 😁

Je vous propose de remplacer la dernière ligne par quelque chose d'un peu plus restrictif :

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

On précise simplement les protocoles autorisés en sortie avec une précaution pour chaque connexion sortante (modulate state).

Vous l'aurez compris, pf nous permet un contrôle de premier ordre sur notre serveur. Dans un premier temps, vous souhaiterez simplement choisir quels ports doivent être ouverts.

Cela fait de nombreuses nouvelles notions, prenez le temps de relire ce qui a été écrit jusqu'ici si besoin.

Lorsque vous serez plus à l'aise, sachez qu'on pourra filtrer le trafic entrant et sortant, et même rediriger une partie de celui-ci vers des services internes (par exemple un anti-spam). Je vous invite à lire la partie "aller-plus-loin-avec-pf". Vous y verrez comment ignorer certaines listes d'IP connues comme nuisibles que l'on enregistrera dans des tables, des règles contre les attaques bruteforce ou encore une proposition d'outil qui ajoute sur liste noire d'éventuels pirates en temps réel. Jetez aussi un oeil à la page du wiki consacrée à pf.

Je vous laisse construire votre fichier /etc/pf.conf selon vos besoins. Partez de l'exemple vu au-dessus puis ajoutez les ports à ouvrir pour commencer. N'hésitez pas à consulter l'exemple fourni à la fin du document.

Pour être sûr, on peut avoir un exemple pour ouvrir le port SSH ?

Si le port SSH configuré est le 222, alors vous aurez dans la configuration de pf :

pass in quick on egress proto tcp to port 222

Bien sûr, vous pouvez aussi vous contenter de modifier uniquement la variable tcp_pass :

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

Commandes pratiques pour pf

Je vous propose une liste de commandes utiles pour gérer pf :

Aller plus loin avec pf

pf-badhost : liste noire d'IP

Certaines IP sont connues pour être nuisibles. Entre celles qui scannent pour trouver un port ouvert ou celles qui tentent des attaques par bruteforce, la liste est longue. Heureusement, on peut configurer le parefeu pour qu'il ignore totalement ces IP et ainsi protéger et alléger la charge de notre serveur.

C'est ce que permet le projet pf-badhost dont je vous invite à lire la page:


Ci-dessous, je vous propose une traduction pour mettre ce dispositif en place.

Il consiste en un script qui récupère une liste d'IP qui seront ensuite ignorées par pf.

On commence par créer un utilisateur dédié à ce script. On l'appelle _pfbadhost. Cela permet de séparer les privilèges, ce qui est bienvenu, même si les étapes intermédiaires pourraient être évitées si on faisait tout en tant que superutilisateur (mais non 😋) :

# useradd -s /sbin/nologin -d /var/empty _pfbadhost 

On télécharge et installe le script pfbadhost avec les bonnes permissions :

# ftp
# install -m 755 -o root -g bin /usr/local/bin/pf-badhost

On doit ensuite créer les fichiers nécessaires au bon fonctionnement du script (liste des IP et logs):

# install -m 640 -o _pfbadhost -g wheel /dev/null /etc/pf-badhost.txt
# install -d -m 755 -o root -g wheel /var/log/pf-badhost
# install -m 640 -o _pfbadhost -g wheel /dev/null /var/log/pf-badhost/pf-badhost.log
# install -m 640 -o _pfbadhost -g wheel /dev/null /var/log/pf-badhost/pf-badhost.log.0.gz

L'auteur conseille d'installer les programmes ripgrep et mawk pour de meilleurs performances. C'est facultatif:

# pkg_add ripgrep mawk

Ajoutez ces lignes dans le fichier /etc/doas.conf afin que l'utilisateur _pfbadhost puisse recharger la liste des IP à blacklister dans le parefeu :

permit root
permit nopass _pfbadhost cmd /sbin/pfctl args -nf /etc/pf.conf
permit nopass _pfbadhost cmd /sbin/pfctl args -t pfbadhost -T replace -f /etc/pf-badhost.txt
# Optional rule for authlog scanning
permit nopass _pfbadhost cmd /usr/bin/zcat args -f /var/log/authlog /var/log/authlog.0.gz

Faîtes en sorte que l'utilisateur _pfbadhost lance le script toutes les nuits à minuit en modifiant son crontab :

# crontab -u _pfbadhost -e
~ 0~1 * * *	-s pf-badhost -O openbsd

Ainsi, pf-badhost sera lancé chaque nuit entre minuit et 1h du matin. Cela permet d'éviter que tous les utilisateurs de pf-badhost n'utilisent ce script en même temps et surcharger les serveurs.

La ligne est à écrire avec vi.


Ajoutez ces lignes au fichier /etc/pf.conf pour que pf tienne compte de la liste noire :

table <pfbadhost> persist file "/etc/pf-badhost.txt"
block quick on egress from <pfbadhost>

Lancez le script pour la première fois comme si vous étiez l'utilisateur _pfbadhost :

# doas -u _pfbadhost pf-badhost -O openbsd

Notez que vous devez pouvoir utiliser doas. Enfin, rechargez le parefeu puis relancez le script :

# pfctl -f /etc/pf.conf
doas -u _pfbadhost pf-badhost -O openbsd

# Configuration de pf-badhost

Pour configurer pf-badhost, vous devez éditer directement le script et chercher ce qui est en-dessous de la ligne

### User Configuration Area -- START

Vous pourrez notamment décommenter (retirer le "#") pour une liste spécifique ou activer l'IPv6.

Lisez le manuel :


Une méthode qu'utilisent les pirates pour s'en prendre à un serveur s'appelle "bruteforce". Ces attaques consistent à essayer toutes les combinaisons d'identifiants/mot-de-passe possibles jusqu'à tomber sur la bonne.

Si vous avez choisi des phrases de passe robustes, vous êtes normalement à l'abri. Toutefois, ces attaques utilisent inutilement des ressources sur votre serveur et peuvent potentiellement réussir un jour (avouez que ça serait pas de bol).

Afin de déjouer ces attaques, il faudrait prendre le temps de décortiquer les journaux des services que vous hébergez (dans /var/log et /var/www/logs) à la recherche d'échecs d'identification et repérer d'où viennent ces attaques pour en bannir les auteurs. Mais soyons honnête, c'est à la fois pénible et chronophage.

L'idéal serait un outil qui surveille les journaux à votre place et s'occupe de mettre sur la liste noire de votre parefeu les IP des "attaquants". Ça tombe bien, vous pouvez utiliser vilain ou sshguard qui sont faits pour ça.

En attendant, pf intègre déjà une protection qui limite le nombre de connexions sur un port pendant un temps donné. C'est très efficace et reste léger. Lisez le paragraphe suivant 😉.

Anti-Bruteforce intégré à pf

pf dispose d'une fonctionnalité très intéressante : les "tables" (tableaux). Ça va nous permettre de garder en mémoire certaines adresses IP de pirates qui tenteraient de compromettre le serveur. Par exemple, pour protéger le service SSH, on va procéder ainsi :

Ça nous donne donc ceci :

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)

Notez que nous avons enregistré le numéro du port utilisé pour le serveur SSH. C'est inutile, car on peut mettre à la place de "$ssh_port" simplement ssh, c'est-à-dire le nom du service. Cependant, on peut vouloir changer le port par défaut du serveur SSH, comme décrit dans le chapitre sur ce service.

Voici un autre exemple pour un site web (ports http et https) :

http_ports = "{ www https }"         # ports http(s)
# Si + de 40 connexions toutes les 5 secondes sur les ports http(s)
# ou si elle essaie de se connecter + de 100 fois
# on ajoute l'ip pour la bloquer.
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)

En temps réel avec vilain

Je me permets de vous proposer un outil que j'ai écrit avec l'aide de Vincent Delft dans le but de mettre sur liste noire d'éventuels pirates lorsque l'attaque a lieu. Vos retours d'utilisation permettront de l'améliorer.

Le script en question s'appelle vilain. Vous pouvez suivre le lien suivant pour consulter le code.

le code

Voici comment l'installer. Tout d'abord, on télécharge l'archive dans le dossier /tmp :

# cd /tmp
# ftp -o vilain.tgz

On décompresse l'archive :

# tar xvzf vilain.tgz

Ensuite, on procède à l'installation :

# cd vilain-master*
# make install

Afin que vilain puisse fonctionner, ajoutez une table à la configuration de pf afin de retenir les IP des attaquants. Éditez le fichier /etc/pf.conf pour y mettre :

table <vilain_bruteforce> persist
block quick from <vilain_bruteforce>

Rechargez pf avec # pfctl -f /etc/pf.conf puis éditez le fichier de configuration de vilain /etc/vilain.conf. Vous y trouverez quelques exemples de la forme suivante :

[nom du gardien]
logfile = /chemin/du/journal/a/inspecter
regex = expression reguliere qui retourne l'IP de l'attaquant en cas d'echec

D'autres options sont disponibles, comme le nombre d'échecs avant le bannissement, les IP à ignorer s'il y a une erreur...

Afin de lancer vilain, vous avez besoin de python3. Installez le paquet python-3.7.* par exemple.

Vous pouvez maintenant activer et lancer vilain comme n'importe quel service :

# rcctl enable vilain
# rcctl start vilain

Si vous consultez les journaux dans le fichier /var/log/daemon, vous y verrez des messages comme :

Start vilain for ssh
Start vilain for ssh2
Start vilain for http401
Start vilain for smtp
Start vilain for dovecot

vilain est en train de protéger votre serveur.

Vous pouvez si vous le souhaitez lancer régulièrement cette commande pour retirer les IP bannies depuis trop longtemps :

pfctl -t vilain_bruteforce -T expire 86400

Pour voir les IP bannies, saisissez :

pfctl -t vilain_bruteforce -T show

En temps réel avec sshguard

Contrairement à ce que son nom indique, sshguard est en mesure de vérifier les tentatives de connexions sur plusieurs services, pas seulement SSH.


Son installation sous OpenBSD n'est pas très compliquée à mettre en place. Comme pour vilain, vous devrez créer une table dans pf qui contiendra toutes les IP mises sur liste noire. Ainsi, on ajoute dans le fichier /etc/pf.conf une nouvelle table contenant les IP des pirates qui sont aussitôt bloquées :

table <sshguard> persist
block in from <sshguard>

Rechargez le parefeu avec pfctl -f /etc/pf.conf.

Maintenant, on installe sshguard comme on a l'habitude de le faire :

# pkg_add sshguard

Copiez le fichier de configuration donné en exemple :

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

Éditez ce fichier si vous le souhaitez pour le rendre plus ou moins sévère. Les options par défaut sont plutôt raisonnables.

Nous pouvons maintenant activer et lancer sshguard :

# rcctl enable sshguard
# rcctl start sshguard

Et voilà, votre serveur est désormais sous bonne garde.

Vous voudrez peut-être mettre sur liste blanche quelques adresses IP avec l'option "-w". Procédez ainsi (cette option peut être employée plusieurs fois).

rcctl set sshguard flags -w -w


Chercher son interface réseau
Exemple de fichier pf.conf
wiki sur pf
Créer des phrases de passe robustes
Vincent Delft