OpenBGPD + RTBH = dynamic PF rules

What if you could dynamically update PF tables across all your hosts? Here is one way you can...

OpenBGPD + RTBH = dynamic PF rules
Photo by Andrew Kliatskyi / Unsplash

If you have been networking for a while you will come across the need to route
traffic to Null0 (black hole).  For the uninitiated, this means that traffic will just be
dropped by the router.   What if there was a way to automate adding these routes to firewall rules.  Good news!  There is.

OpenBGPD is capable BGP daemon in it's own right.  It has a hidden capability.  It can add routes to a PF table.

Why would you want to use this if you do not run a large routing network?  Glad you asked.  Let's say you have several servers on a hostile network doing server stuff (email, web, blog, etc). And being a good admin the have things pretty locked down.  The servers keep getting harassed by ne'er-do-wells, filling logs with noise, etc. By using RTBH and OpenBGPD, harassment of one server is a block on all servers.

Headend Setup

First thing is to configure the BGP Headend router.  This router, and in fact
all of the routing daemons, will not update the routing tables on the systems. They
are just setup to exchange routes between each other.

# The ASN does not need to be registered.  In fact having it not registered
# will add protection that these records will not slip out into the
# greater Internet.
# The RTBH community can also be anything we want.
# This list cooresponds to the list of PF tables.  There can be as many or
# few as required.
# Local
#  001 sshguard
#  002 banned
#  003 aggressive

# global configuration
# prevent adding routes to host routing table
fib-update no
nexthop qualify via default

# Devices to peer with. Use of a password, may allow the neighbors to be
# wild carded, but may increase risk.
group "peers" {
        announce IPv4 unicast
        announce IPv6 unicast
        remote-as $ASN
        # since we are utilizing iBGP, we need to have the headend
        # be the route-reflector so we will not need a full mesh.
        # Let out peers come to us.
        tcp md5sig password "super secure password here"
        neighbor <IP address of spoke>
# Allow iBGP routes in and out.  If this configuration is part of a router then
# the allow filters will be more involved.
allow from ibgp
allow to ibgp
# Match any routes that have the RTBH community and add them to the rtbh table.
# Enable if headend box will also participate in RTBH.  Dont forget to update
# the PF config.  Will need, at a minimum, a rtbh table.
#match from ibgp community $RTBH:* set { pftable rtbh }
headend bgpd.conf

Now enable BGP on the headend and start it.

rcctl enable bgpd
rcctl start bgpd

Spoke Setup

Next each spoke will need 3 parts: PF config update; BGP config; and a helper program. The first part is an update to the PF rules running on the device.  The
following updates will add PF table(s) and a drop rule.

###### partial PF configuration
# store the routes learned from BGP
table <rtbh> persist
# locally banned hosts added manually
table <banned> persist
# aggressive hosts added automatically by PF
table <aggressive> persist

block quick from <rtbh> label "rtbh host"
block quick from <banned> label "banned host"
block quick from <aggressive> label "aggressive host"

Validate and restart PF.

pfctl -nf /etc/pf.conf && pfctl -f /etc/pf.conf

The second part is the BGP config.  This config will be exactly the same on all
the spoke servers.

# Almost exactly the same as the headend config
# Local
#  001 sshguard
#  002 banned
#  003 aggressive

# global configuration
fib-update no
nexthop qualify via default

group "peers" {
        announce IPv4 unicast
        announce IPv6 unicast
        remote-as $ASN
        tcp md5sig password "super secure password here"
        neighbor <IP address of headend>
allow from ibgp
allow to ibgp
match from ibgp community $RTBH:* set { pftable rtbh }
spoke bgpd.conf

Now enable and start bgpd.

rcctl enable bgpd
rcctl start bgpd

Now for the last part... the watcher daemon.  OpenBGPD has the ability to add
and remove addresses/networks using a PF table.  However, it does not monitor
PF tables for additions/subtractions that are made outside the BGP daemon.
This is where the watcher daemon comes in.  It watches one or more tables for
additions/subtractions and utilizes bgpctl commands to add/remove routes as
they are added and removed from the table(s).

The script can be downloaded from github.  Word of warning, this is a
minimal viable script. There are probably better programs out there. (If you
know of any feel free to drop me an email.) However, it is lightweight and
should run on any flavor of  *nix out there.

Assuming you are configuring OpenBSD, there is a rc.d script. Enable the
daemon, set the tables to watch, and then start.

rcctl enable watchtabled
rcctl set watchtabled flag -t banned:65535:102 -t aggressive:65535:103
rcctl start watchtabled

Now with everything running lets take a look at the tables.

pfctl -T show -t table1 | wc -l

Typing this can get tedious quickly.  I created a this script to help.

One thing we should notice is there are no records.  While we have setup the
environment to watch and replicate records, we need to add records.  There are
several ways this can happen.  The most straight forward is to add them
manually to the table.

pfctl -T add -t banned

To see if the address is in the BGP daemon, use the bgpctl command.  NOTE:
that the community filter will need to match the community that references the
specific PF table.

bgpctl show ip bgp community 65535:102

Another option for adding records more automatically is to utilize the PF
'overload' option.

#### partial PF config

pass in inet proto tcp from any to ($ext) port ssh keep state \
  (source-track rule, max-src-conn-rate 20/10, \
   overload <aggressive> flush global)

Here aggressive hosts will trigger an addition to the aggressive PF
table.  NOTE: the max connection rate rule will need to be tailored to the
service.  SSH on a console server is much different that SSH on an Email

Last, but not least, is the use of some other application like sshguard or
fail2ban.  The setup of these services is outside the scope of this post, but
once setup to add 'bad hosts' to PF tables, these tables can be watched and
trigger RTBH bans.


Hopefully this article has help explain remote triggered blackholes and how they can be used to protect multiple systems from outside attacks.