Using pf on OS X Mountain Lion

I’ve written before about adding an extra layer of network security to your Macintosh by leveraging the BSD-level ipfw firewall, in addition to the standard GUI firewall and additional third-party firewalls (like Little Snitch). In OS X Lion and OS X Mountain Lion, though, ipfw was deprecated in favor of pf, the powerful packet filter that I believe originated on OpenBSD. (OS X’s version of pf is ported from FreeBSD.) In this article, I’m going to show you how to use pf on OS X.

Note that this is just one way of leveraging pf, not necessarily the only way of doing it. I tested (and am currently using) this configuration on OS X Mountain Lion 10.8.3.

There are X basic pieces involved in getting pf up and running on OS X Mountain Lion:

  1. Putting pf configuration files in place.
  2. Creating a launchd item for pf.

Let’s look at each of these pieces in a bit more detail. We’ll start with the configuration files.

Putting Configuration Files in Place

OS X Mountain Lion comes with a barebones /etc/pf.conf preinstalled. This barebones configuration file references a single anchor, found in /etc/pf.anchors/com.apple. This anchor, however, does not contain any actual pf rules; instead, it appears to be nothing more than a placeholder.

Since there is a configuration file already in place, you have two options ahead of you:

  1. You can overwrite the existing configuration file. The drawback of this approach is that a) Apple has been known to change this file during system updates, undoing your changes; and b) it could break future OS X functionality.

  2. You can bypass the existing configuration file. This is the approach I took, partly due to the reasons listed above and partly because I found that pfctl (the program used to manage pf) wouldn’t activate the filter rules when the existing configuration file was used. (It complained about improper order of lines in the existing configuration file.)

Note that some tools (like IceFloor) take the first approach and modify the existing configuration file.

I’ll assume you’re going to use option #2. What you’ll need, then, are (at a minimum) two configuration files:

  1. The pf configuration file you want it to parse on startup
  2. At least one anchor file that contains the various options and rules you want to pass to pf when it starts

Since we’re bypassing the existing configuration file, all you really need is an extremely simple configuration file that points to your anchor and loads it, like this:

The other file you need has the actual options and rules that will be passed to pf when it starts. You can get fancy here and use a separate file to define macros and tables, or you can bundle the macros and tables in with the rules. Whatever approach you take, be sure that you have the commands in this file in the right order: options, normalization, queueing, translation, and filtering. Failure to put things in the right order will cause pf not to enable and will leave your system without this additional layer of network protection.

A very simple set of rules in an anchor might look something like this:

Naturally, you’d want to customize these rules to fit your environment. At the end of this article I provide some additional resources that might help with this task.

Once you have the configuration file in place and at least one anchor defined with rules (in the right order!), then you’re ready to move ahead with creating the launchd item for pf so that it starts automatically.

However, there is one additional thing you might want to do first—test your rules to be sure everything is correct. Use this command in a terminal window while running as an administrative user:

sudo pfctl -v -n -f <path to configuration file>

If this command reports errors, go back and fix them before proceeding.

Creating the launchd Item for pf

Creating the launchd item simply involves creating a properly-formatted XML file and placing it in /Library/LaunchDaemons. It must be owned by root, otherwise it won’t be processed at all. If you aren’t clear on how to make sure it’s owned by root, go do a bit of reading on sudo and chown.

Here’s a launchd item you might use for pf:

A few notes about this launchd item:

  • You’ll want to change the last <string> item under the ProgramArguments key to properly reflect the path and filename of the custom configuration file you created earlier. In my case, I’m storing both the configuration file and the anchor in the /etc/pf.anchors directory.
  • As I stated earlier, you must ensure this file is owned by root once you put it into /Library/LaunchDaemons. It won’t work otherwise.
  • If you have additional parameters you want/need to pass to pfctl, add them as separate lines in the ProgramArguments array. Each individual argument on the command line must be a separate item in the array.

Once this file is in place with the right ownership, you can either use launchctl to load it or restart your computer. The robust pf firewall should now be running on your OS X Mountain Lion system. Enjoy!

Some Additional Resources

Finally, it’s important to note that I found a few different web sites helpful during my experimentations with pf on OS X. This write-up was written with Lion in mind, but applies equally well to Mountain Lion, and this site—while clearly focused on OpenBSD and FreeBSD—was nevertheless quite helpful as well.

It should go without saying, but I’ll say it nevertheless: courteous comments are welcome! Feel free to add your thoughts, ideas, questions, or corrections below.

Tags: , , ,

  1. Tim’s avatar

    Hey Scott,

    This looks interesting, ill try it later. I have a question though, what would be the advantages of going this route vs using the firewall in system preferences?

    Thanks,

  2. slowe’s avatar

    Tim, the advantage here is that pf offers much greater granularity than the firewall in System Preferences. For example, with pf I can allow traffic used by iTunes, but only from specific network addresses. In System Preferences, the traffic is either allowed or denied.

    However, keep in mind that this additional flexibility also means additional complexity, and that might deter the use of pf for more “casual” OS X users.

    I hope this helps!

  3. Lennie’s avatar

    Scott,

    My experiences with Macs are limited, but I do have experience with pf on OpenBSD.

    It is interresting how they use the anchor, normally you’d just use an include.

    Anchors are usually used to dynamically add/remove rules from for example a userspace program. The name of the anchor would for example be the name of the program, relayd is an example of such a program: http://www.openbsd.org/cgi-bin/man.cgi?query=relayd&sektion=8&arch=&apropos=0&manpath=OpenBSD+Current

    I wouldn’t be surprised if on the Mac they are actually dynamically adding rules as well.

    You can probably see that if you use these commands when pf is enabled:

    pfctl -vvsr # to see the current rules
    pfctl -vvss # to see the current state entries

  4. phocean’s avatar

    Hello Scott,

    Did you test your config? Like a scan or running a netcat server on some port, and try to connect to it from the outside with and without pf enabled ?

    Because it happens that on my system, pf is not filtering anything at all.
    I was suspicious of my machine, so I even installed a fresh VM and reproduced the same issue: with a proper and valid anchor file (as simple as “block all”), pf enabled using the correct pf.conf, there is no filtering and pf actually sees no traffic (checked using pflog0).

    Are you absolutely sure it is working for you? Any idea?

  5. phocean’s avatar

    Oops! Never mind, just found my mistake:
    anchor “test/*”
    instead of:
    anchor “test”

    An unfortunate copy and paste from other lines of pf.conf.

  6. Pie’s avatar

    Unfortunately, it doesn’t work with any non-trivial setting(main reason you are bothering to manually configure pf). Even simplest NAT causing problem. Launchctl starts pf rather early during boot, and if you have DHCP or PPP interfaces, pf just fails to launch.

  7. slowe’s avatar

    Pie, I use DHCP on my interfaces and pf seems to be working fine (pfctl reports packets being evaluated, which implies to me that it’s working). Can you provide more information?

  8. Pie’s avatar

    If you have something like:
    nat pass on ppp0 from en1:network to any -> (ppp0) port 1024:65535
    You will get: IP address found for en1:network, pfctl: Syntax error in config file: pf rules not loaded.
    It means pf is trying to start before en interfaces is up.
    Generally, you should be able to manipulate boot sequence to make pf work properly in all cases.

  9. Pie’s avatar

    IP address not found for en1:network
    I’ve lost “not” during copy-paste somehow.

  10. Lennie’s avatar

    Then this is a bug in Mac OS X, I don’t think I’ve seen this problem before with any other OS which uses PF. Normally with PF the OS would create something like an anchor and dynamically add/remove addresses/interfaces on the anchor as they get added/removed.

  11. Daniel Lord’s avatar

    There seems to be a command for ipconfig to ensure pf waits for all interface to come up. IceFloor uses it in the LaunchDaemon file (/etc/icefloor.sh). Is it broken or have you not tried specifying it in you Launch Daemon configuration?

    #
    # Use the “ipconfig waitall” command to wait for all the interfaces to come up:
    #
    ipconfig waitall

  12. Pie’s avatar

    @Daniel Lord
    I’m not sure it’s possible to stuff any shell command to formatted LaunchDaemon file.

  13. Daniel Lord’s avatar

    @Pie said “I’m not sure it’s possible to stuff any shell command to formatted LaunchDaemon file.”

    Correct, IceFloor calls a shell script (/etc/icefloor.sh) on launch that submits the command. If you are unfamiliar with IceFloor is a GUI pf management tool that is pretty powerful and makes pf administration simple, at least for me, compared to hand editing the text files. I chose to put my energy into other areas than pf configuration and maintenance. But to each his own labor.

  14. slowe’s avatar

    Daniel, Pie, great conversation! Thanks for sharing useful information. FWIW, I don’t find my manual pf configuration to be a lot of work after the initial setup (and I did take a look at IceFloor).

  15. Pie’s avatar

    @slowe
    I prefer manual configuration too.
    It’s pretty silly to install 3rd party software(bugs included), if you need one line pf config. IceFloor has good documentation about MacOSX firewalls, but I don’t like the implementation. It’s hard to find good literature about current MacOSX boot details and most information comes from MacServer discussions. Do you have any good sources ?

  16. Daniel Lord’s avatar

    “WIW, I don’t find my manual pf configuration to be a lot of work after the initial setup (and I did take a look at IceFloor).”

    Exactly—*after initial configuration*. Provided you know what you are doing or don’t mind spending time debugging instead of developing stuff. I run several VMs on a couple remote systems with different servers and ports, 2 Raspberry Pis with LAMP stacks with Tomcat, and a Sonos music system on my LAN. I just wanted everything opened up as much as needed but no more so I let Ice Floor brute force it for me until I know enough not to mess it up. Ice Floor is quirky and the GUI is not the best design for usability, but it got me into a quick & dirty up and running state. I have heard Apple is behind on the versions on some pf components (Apple does this a lot is seems) and that can lead to mis-configuration if you follow on-line docs for the current versions out there. Configurer emptor ;-)

  17. Richard’s avatar

    Scott, others, Do you know if it is possible to use the PF firewall to force all HTTP traffic on a machine through a Proxy server? I realize this can be done via the web browser, but i’d like to avoid configuring multiple browsers and I desire make it difficult for end-users to disable the the redirection. I tried for several days to create a such a configuration file without success.

  18. slowe’s avatar

    Richard, is it possible? Most likely. Do I know how to do it? Nope. :-) Perhaps more knowledgeable readers could pipe in with suggestions or recommendations.

  19. Fabio Martins’s avatar

    Richard,

    I’ve done that in the past (not on OS X) using this:
    http://www.benzedrine.cx/transquid.html

    benzedrine.cx is Daniel Hartmeier’s (PF author) personal web site.

    For HTTPS you will need other approaches, though …

  20. Carlos’s avatar

    Hi!
    I just want to send socket packages (or ping) my wireless device (tablet) connected from the “Internet Sharing” of the Desktop Mac (sharing en0 to en1 through bridge0). Say, on port 1234. This would be easy if I can use a Wireless router and both devices were on the same Lan but I am not allowed to do that. When sharing the connection, I got 192.168.2.2 on the tablet but cannot ping or connect a socket. I tried your instructions and used the rule:
    rdr pass log proto tcp from any to any port 1234 -> 192.168.2.2 port 1234
    Without luck (tried to open a connection to my lan addres at port 1234). I would appreciate any ideas.

  21. David’s avatar

    FYI According to the Ice Floor bulleted feature list now, IceFloor uses its own configs “IceFloor uses its own set of PF configuration files; default OS X PF configuration files are not modified “

  22. Kent’s avatar

    Scott, I’m not sure what I’m doing wrong. But, I’ve been trying to get PF to start on boot for a longtime. PF works fine when I manually start it. The “plist” file seems fairly self-explanitory. I dropped it in “/Library/LaunchDaemons”. That was a no go. I dropped it in “/System/Library/LaunchDaemons”. That didn’t work either. I had the same problem with ipfw prior to using PF.
    Any suggestions?
    Thanks.

  23. slowe’s avatar

    Kent, double-check the file ownership and permissions on the plist file. It should match the other files in the same directory.

  24. Kent’s avatar

    Thanks for the reply. The files do have the same ownership and permission as the others within the directory.

  25. Mark’s avatar

    Hello Scott, thank you for this very useful post.
    The official openBSD guide, tells if you add name servers addresses to rules, they are translated using dns when rules are loaded, not when pf is started.
    (http://www.openbsd.org/faq/pf/config.html)
    My question is: is it possible to load rules only when connection to specified address is started. Example: i want to resolve google.com only when trying to connect to google.com and not at PF startup.
    Thanks.

  26. Mark’s avatar

    I’m sorry, the page where i got the info I was referring in my last comment is this:
    http://www.openbsd.org/faq/pf/filter.html

  27. Bill O'Donnell’s avatar

    Thanks for this nice summary. I was able to secure my server after upgrading from 10.6.8 to 10.10 today. And I only locked myself out one time ;-)

Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>