Setting up an OpenBSD firewall


 

 

 

 

 

Introduction to this web page

My two Red Hat 9 based Linux firewalls have provided excellent service - one of them has worked faultlessly for over two years now, the other one has worked faultlessly since it was built several months ago.

However time moves on, and the kernel they use, which is version 2.4.20, is now old hat, and anyway, even with the recompiling I did, it has a bug that affects the state table used by packet forwarding.

So it was time to think about replacements - I thought about going with Red Hat Fedora which is based on kernel 2.6, however Red Hat Fedora is getting more and more bloated, so I finally went down the road of OpenBSD and pf - as far as using it as a firewall is concerned, OpenBSD has a distinct advantage over most other open source unix look-alikes - it is designed from the basement up with security as the primary design parameter.

So this web page is about setting up an OpenBSD firewall - in this case this is OpenBSD 3.8 on i386 architecture.

 

The function of the firewall

The two OpenBSD firewalls I have built are direct replacements for my previous Linux firewalls.

In each case the firewall box has an external ethernet network interface which eventually connects to the open internet through a series of routers.

Also, in each case the firewall box has an internal ethernet network interface connected to a secure subnet which hosts a small number of pc`s. The secure subnet uses one of the reserved ip network addresses, ie, 192.168.100.0/24. So the firewall box is doing Network Address Translation, as well as ip address based packet filtering and stateful packet filtering.

 

Installing OpenBSD

The first challenge was to install OpenBSD - if you think Linux is for geeks, try OpenBSD !!

Installation is far from being intuitive. The easiest ( or least difficult ) way is to install from a cd, which is what I did.

Here is the step by step process that worked for me, on an Intel 500 MHz P3 with two 10/100 Intel network cards. :-

Insert cd, make sure the BIOS is set to boot from the cd

Once the scrolling stops, you should have a prompt

     (I)nstall, (U)pgrade or (S)hell?  

Type I to select install, next prompt is

     Terminal type? [vt220]     

Accept the default by pressing "Return", next prompt is

     kbd(8) mapping? (`?` for list) [none]   

This is asking you what keyboard mapping you require - if you want a UK mapping, then enter "uk" and press "Return"

If you want a mapping from a different country, enter "?", and choose the one you want.

You now get a warning about data on the computer - you are about to wipe your hard drive !

     Proceed with install? [no]   

The default is no, so type "y" to proceed, and the installation program then goes off and looks for your hard drive or drives. In my case, the hard drive type is called "wd", and they are numbered from 0 upwards. As I had only one hard drive, it is known as wd0.

Press "Return" to accept wd0

     Do you want to use *all* of wd0 for OpenBSD? [no]   

I guess this is self-explanatory, the following assumes that you type "y" to have the install program re-partition the whole hard drive. If you want to preserve some existing partitions, then type "n" and now you`re own your own !

Read the next bit of text carefully - you are going to have manually divide up your hard drive into several OpenBSD partitions using the OpenBSD label editor.

Having typed "y", the prompt changes to a chevron, and you are now within the label editor.

Typing "?" at the chevron prompt gives a list of commands.

Type "p", to get a printout of the current hard drive configuration. At the bottom of the printout, you will see a number of lines that look roughly like

     a:     16498692         63    unused      0     0
     c:     16514064          0    unused      0     0    

In my case, a: is the existing single primary partition.

In every case, c: is the hard drive itself - this is of course quite different from Microsoft Windows, where C: is always the active primary partition.

We need to get rid of a:, so at the chevron prompt, type

     d a   

and now doing another printout, we now only have

     c:     16514064          0    unused      0     0    

Don`t try to delete c:

We can now start to add our OpenBSD partitions - and here a little arithmetic is required. To maximise the security of your Open BSD installation, the designers of OpenBSD advise that some parts of the directory structure should be given their own partitions.

I set up the following partitions and mount points :-

            a:                 /

            b:                 swap space

            d:                 /usr

            e:                 /var

            f:                 /tmp

            g:                 /home

I think that by convention, a: is always set to the root directory.

Partition b: is always swap space, the label editor is configured this way.

Partition c: is always the whole hard drive, so don`t try to change it.

We need to decide how large to make each partition - I started with a 10GB hard drive, so to make life easy, I just gave each partition 1GB, except the swap partition, which I made 250MB.

So now back to our label editor - start by creating the a: partition -

     a a    

We get the response -

     offset: [63]    

This is asking you to specify the start point of the partition - just press "Return" to accept the start point it is offering.

In my case, it responds with

     size: [16498692]    

We definitely do not want to accept the size it is offering, as it wants to use the whole free space on the hard drive. Fortunately we don`t have to think in terms of sectors, we can specify a size in Megabytes, so for a 1GB partition, we can type in

     size: [16498692] 1000M    

and it responds with

     Rounding to nearest cylinder: 2048193
     FS type: [4.2BSD]      

So it has more or less accepted our size request. Then it asks us about the file system to go on this partition - press "Return" to accept the default. Now it is looking for a mount point :-

     mount point: [none]    

As we are setting up the partition for the root directory, we can respond with

     mount point: [none]  /  

After pressing return, we are back to the chevron prompt, so that partition is setup, and we can start on the next one, which is b:, ie, the swap partition. The label editor responds differently when setting up the b: partition - the b: partition is always swap space

     a b     

After pressing "Return", we get the response

     offset: [2048256]     

Accept this by pressing "Return" again, and then enter the required size -

     size: [14450499]  250M  

After pressing return, we get

     Rounding to nearest cylinder: 512064
     FS type: [swap]      

After pressing "Return" again, we are back to our chevron prompt, and can do the next partition. So repeat the process that was used to create a:, in order to set up d:, e:, f:, and g:, with the mount point as shown above. Once this is done, entering "p" at the chevron prompt should give us the response

     a:      2048193             63      4.2BSD       2048      16384     16   #  /
     b:       512064        2048256        swap
     c:     16514064              0      unused          0          0
     d:      2048256        2560320      4.2BSD       2048      16384     16   #  /usr
     e:      2048256        4608576      4.2BSD       2048      16384     16   #  /usr
     f:      2048256        6656832      4.2BSD       2048      16384     16   #  /usr
     g:      2048256        8705088      4.2BSD       2048      16384     16   #  /usr   

We now need to save all this, so press "q" - we get the response

     Write new label?: [y]      

Press "Return" to confirm,and we get a whole new type of response

     Mount point for wd0d (size=1024128k)? (or `none` or `done`) [/usr]   

Pressing "Return" gives us another similar line, etc, for as long as you can be bothered pressing "Return". A better response is for us to type "done". We get a summary of what we have done, and a request for confirmation.

Press "y" to confirm, then sit and watch it creating the partitions !

Now it looks for a system hostname, then asks to configure the network, which of course we do. After pressing "Return", it gives us a list of available interfaces. In my case, as I am using Intel network cards, it identifies these as "fxp0" and "fxp1". We might as well start with fxp0, since that is offered as the default. Again, in my case fxp0 is the network interface that will be connecting to the internet, so it gets configured with the ip address and subnet details of the subnet to which the box is attached.

The questions that it requires answers to are fairly self-explanatory, so I shall not list them here. You can invent a symbolic host name for each interface, and you probably don`t need to change the media options. After doing fxp0. just repeat for fxp1.

The default IPv4 route is the ip address of the gateway of the external subnet to which the box is connected.

You probably don`t want to edit hosts with ed, and you shouldn`t need to do any manual network configuration.

     Let`s install the sets!
     Location of sets? (cd disk ftp http or `done`) [cd]      

This actually refers to the software you want installed. If you are using a cd, then just accept the default of cd, then accept the default of cd0 ( if you only have one cd player ), then accept the default path.

You are now offered a list of software which can be installed - for a firewall, the default selection will do fine. It may be possible to set up a firewall with less software than the default selection, but I haven`t tried it yet. To accept the default, just enter "done" at the prompt. Then accept the default response of [yes], and the software gets installed.

Once that is finished, we don`t want to install any more software, so accept the default of [done].

     Start sshd(8) by default? [yes]     

It is up to you if you want to run ssh by default, so make the appropriate response, and again for ntp.

     Do you expect to run the X Windows System? [yes]    

On a firewall, definitely no !

Accept the default for the default console.

For the UK, the timezone setting is GMT.

Follow the instructions to "halt" and then reboot, and you are finished.

 

Configuring OpenBSD for pf

pf is the recognised abbreviation for Packet Filter - the system used in OpenBSD for filtering IP traffic and doing network address translation.

Having installed the default configuration of OpenBSD, we now have to configure it so that pf works. There isn`t a lot to do, it only involves some small changes to some OpenBSD configuration files. We can use /bin/vi to do this.

Start with /etc/rc.conf - look for a line about halfway down that says

     pf=NO         # Packet Filter / NAT   

Change this to

     pf=YES         # Packet Filter / NAT   

to enable pf at bootup time.

Now scroll further down this file, to just before the bottom - look for a line

     pf_rules=/etc/pf.conf         # Packet filter rules file   

This sets the default configuration file for pf, which you can change if you want. However whichever file is specified here is the one that needs to contain the ruleset for our firewall, which ruleset we still have to write.

Now open up in vi the file /etc/sysctl.conf, and look for the fifth line of text -

     #net.inet.ip.forwarding=1    # 1=Permit forwarding (routing) of packets   

Remove the initial "#" at the start of the line, so the line becomes active. This enables packets to be forwarded between the two network interfaces.

That completes the changes to the OpenBSD configuration files, now we need to look at writing the ruleset that will be saved into pf.conf, or whatever alternative you have chosen.

 

Configuring pf via its configuration file

Just for convenience, I am going to refer to the pf configuration file as pf.conf. But as said above, it doesn`t have to be pf.conf, it can have any filename you want, as long as you remember to put the file name into /etc/sysctl.conf

There is an evil looking script in the default pf.conf file - I personally think it is easier to ignore it altogether - it includes tables, macros, nat, port forwarding, etc, with no explanations of any kind. Start from scratch with an empty file, and build up your own script that you understand.

The pf.conf file should contain 7 sections - these are

                Macros

                Tables

                Options

                Scrub

                Queueing

                Translation

                Filter Rules

It isn`t neccessary to use all 7 sections, but those that are used should be in the above order in the pf.conf script.

Personally, I think that using macros and tables makes it more difficult to understand your script, and makes it much more difficult to match your human view of the configuration of pf with the kernel view. So currently I don`t use either.

There is plenty of documentation available from the OpenBSD website on writing a pf.conf script, so I am not going to try to give instructions here. Instead, here is the script that I am using on one of my firewalls - the script is fully commented. This is still early days for me in understanding OpenBSD and pf, so my scripts will change as my understanding increases. However this is a basic working script, that provides ip address blocking, network address translation, and stateful packet filtering. It also allows a PPTP based VPN to work through the firewall, where it is the VPN client that is behind the firewall on the secure subnet.


     #  This is pf configuration file 

     #  Version = pf-2-hm.conf

     #  -------------------

     #  MACROS

     #  TABLES

     #  OPTIONS

     #  SCRUB

     #  This section cleans up incoming tcp packets


           scrub in all  


     #  QUEUEING

     #  TRANSLATION


           nat on fxp0 inet from 192.168.100.0/24 to any -> 10.0.0.100


     #  FILTER RULES

 
     #  This section sets the default policies to Block


           block in all

           block out all

 

     #  These ip addresses should not appear on the outside of
     #  the firewall


           block in quick on fxp0 from 0.0.0.0/32 

     #     block in quick on fxp0 from 10.0.0.0/8

           block in quick on fxp0 from 127.0.0.0/8 

           block in quick on fxp0 from 172.16.0.0/12

           block in quick on fxp0 from 192.0.2.0/24

           block in quick on fxp0 from 192.168.0.0/16

           block in quick on fxp0 from 240.0.0.0/5

           block in quick on fxp0 from 224.0.0.0/4


     #  NAT occurs before filtering, so secure subnet
     #  network address should not appear outside the firewall


           block out quick on fxp0 from 192.168.100.0/24


     #  Block spoofed ip addresses from pc`s on secure
     #  subnet


           block in quick on fxp1 from ! 192.168.100.0/24

           block in quick on fxp1 from 192.168.100.1




     #  Allow traffic on loopback interface


           pass quick on lo0 all


     #  Set up stateful packet filtering


           pass out on fxp0 inet proto tcp from any to any keep state

           pass out on fxp0 inet proto udp from any to any keep state

           pass out on fxp0 inet proto icmp from any to any icmp-type echoreq keep state 

           pass out on fxp0 inet proto 47 from any to any keep state



     #  Open up fxp1 


           pass in on fxp1 inet from 192.168.100.0/24 to any

           pass out on fxp1 inet from any to 192.168.100.0/24 


In my Linux firewalls, as well as doing ip address blocking, NAT, and stateful packet filtering, I also had a default deny policy for all TCP and UDP ports, and then added rules to allow specific ports through the firewall. Currently I haven`t gone down this road with my OpenBSD + pf firewalls, as it is going to increase very considerably the number of rules that every packet would have to go through. But it may happen in the future.

 

pfctl

In order for humans to interact with pf, OpenBSD provides a tool called "pfctl" which can be used to view the existing configuration of pf, and also make changes on the fly.

NB - making changes on the fly will change the current configuration of pf, but if you do a reboot, pf loses these changes, and goes back to the configuration as specified in the /etc/pf.conf file.

Again, there is lots of documentation on pfctl, so there is no point in me providing full details on this web page, but here are some of the things you can do with it :-

                pfctl -f /etc/pf.conf                 reload the /etc/pf.conf configuration file

                pfctl -sa                                 view all configuration settings

                pfctl -sr                                 view just the filter rules

                pfctl -ss                                 view the state table

This last one, viewing the state table, is somewhat limited, as it only shows a snapshot of the state table at the time you run the command. However the contents of the state table are continuously changing as new packets need to be recorded. It would be great if you could view the state table in real time, so you could see each new entry as it happens. It would make troubleshooting so much easier.

Dream on !

 

That`s it

That is as far as I have got so far, I`ll add some more if I get deeper into pf

 


© 2006 Ron Turner


Return to the Index page