Pf firewall notes

From Wiki

Jump to: navigation, search

A great deal of this is from http://calomel.org but I have altered and condensed it down a bit to suit my own purposes.

Parse the pf.conf file but don't apply it:

# pfctl -nvf /etc/pf.conf

Apply the rules currently in /etc/pf.conf:

# pfctl -f /etc/pf.conf

Disable pf altogether:

# pfctl -d

Re-enable pf and read in config:

# pfctl -ef /etc/pf.conf

Logs are stored in a binary format, must be read with tcpdump or similar:

# tcpdump -n -e -ttt -r /var/log/pflog

Watch things happening in real time:

# tcpdump -n -e -ttt -i pflog0

pf.conf

This is correct as of OpenBSD version 4.6.

Packets are matched against the last rule encountered unless the quick option is used.

Rules have to be in the order macros, tables (not shown), options, scrub (not shown), queues, NAT, filter like so:

ext_if="vr0"
int_if="vr1"
vpn_if="tun0"
6_if="gif0"

## Macros
#  - I don't use these much, I think they *reduce* readability 
webports = "{ http, https }"
tcpworkstationsout = "{ ssh, http, https, domain, rsync, whois }" 
udpworkstationsout = "{ domain, ntp }"
snakebite6 = "2001:470:903f::11"
snakebiteweb6 = "2001:470:903f::12"

## Options
# Ignore the loopback interface
set skip on lo0
# pfstat analyses the external interface
set loginterface $ext_if

## altq qos queues
#   - 'bulk' is the default queue
#   - 1000Kb is the point at which my Be connection's upload gets saturated
 altq on $ext_if bandwidth 1000Kb hfsc queue { iax, ack, dns, ssh, web, mail, bulk, bittor }
  # ack must always be highest priority; without ack going out no data can be received
  queue ack        bandwidth 30% priority 9 qlimit 500 hfsc (realtime   20%)
  queue iax        bandwidth 10% priority 8 qlimit 500 hfsc (realtime   10%)
  queue dns        bandwidth  5% priority 7 qlimit 500 hfsc (realtime    5%)
  queue ssh        bandwidth 20% priority 6 qlimit 500 hfsc (realtime    5%) {ssh_login, ssh_bulk}
   queue ssh_login bandwidth 50% priority 6 qlimit 500 hfsc
   queue ssh_bulk  bandwidth 50% priority 5 qlimit 500 hfsc
  queue web        bandwidth  5% priority 5 qlimit 500 hfsc (realtime  (10%, 2000, 5%) )
  queue bulk       bandwidth 20% priority 4 qlimit 500 hfsc (realtime   20% default)
  queue mail       bandwidth  5% priority 3 qlimit 500 hfsc
  queue bittor     bandwidth  1% priority 2 qlimit 500 hfsc (upperlimit 80%)

## FTP stuff
#   - makes active and passive FTP work
nat-anchor "ftp-proxy/*"
rdr-anchor "ftp-proxy/*"
rdr on $int_if inet proto tcp from $int_if:network to any port ftp -> lo0 port 8021

## rdr (redirect) rules are the port forwarding rules 
rdr on $ext_if inet proto tcp from any to 78.105.97.112 port 80 -> 172.16.0.13
rdr on $ext_if inet proto tcp from any to 78.105.97.112 port 6881:6889 -> 172.16.0.3
rdr on $ext_if inet proto tcp from any to 78.105.97.112 port 50000 -> 172.16.0.3

## nat rules are 'dynamic' NAT, one to many
# Allow hosts from 172.16.0.0/24 to nat through to the internet
nat on $ext_if inet from $int_if:network to any -> 78.105.97.112 

## These are the 'static' 1:1 NAT rules
#    - these are servers that have their own internet-routable IP
binat on $ext_if from 172.16.0.82 to any -> 78.105.100.135 
binat on $ext_if from 172.16.0.2 to any -> 78.105.100.129
binat on $ext_if from 172.16.0.5 to any -> 78.105.100.130
binat on $ext_if from 172.16.0.3 to any -> 78.105.100.132
binat on $ext_if from 172.16.0.13 to any -> 78.105.100.138

## pass and block statements below here
# If we don't specify otherwise lateri, block it and log it 
block log all

# Filtering rule for FTP proxy set up above earlier
anchor "ftp-proxy/*"
pass in on $int_if proto tcp from $int_if:network to lo0 port 8021

# IPv4 connections through to internal hosts
pass in on $ext_if proto tcp from any to 172.16.0.182 port $webports
pass in on $ext_if inet proto tcp from any to 172.16.0.12 port $webports
pass in on $ext_if inet proto tcp from any to 172.16.0.2 port 993
pass in on $ext_if inet proto tcp from any to 172.16.0.2 port 25
pass in on $ext_if inet proto tcp from any to 172.16.0.3 port 53
pass in on $ext_if inet proto udp from any to 172.16.0.3 port 53
pass in on $ext_if inet proto tcp from any to 172.16.0.5 port $webports
pass in on $ext_if inet proto tcp from any to 172.16.0.6 port $webports
pass in on $ext_if inet proto tcp from any to 172.16.0.10 port 80
pass in on $ext_if inet proto udp from any to 172.16.0.20 port 4569 
pass in on $ext_if inet proto tcp from any to 172.16.0.13 port $webports

# Connections from the VPN
pass in on $vpn_if inet proto tcp from 10.1.0.0/24 to 172.16.0.2 port 3128
pass in on $vpn_if inet proto tcp from 10.1.0.0/24 to 172.16.0.2 port 25
pass in on $vpn_if inet proto tcp from 10.1.0.0/24 to 172.16.0.2 port 993

# IPv6 connections through to internal hosts 
pass in on $6_if inet6 proto tcp from any to $snakebiteweb6 port 80
pass in on $6_if inet6 proto { tcp, udp } from any to $snakebite6 port domain

# Connections to the firewall itself
# OpenVPN
pass in on $ext_if inet proto udp from any to 78.105.97.112 port 1194
pass in on $ext_if inet proto tcp from any to 78.105.97.112 port 22
# pfstatd - for monitoring
pass in on $int_if inet proto tcp from $int_if:network to $int_if port 9999

# Pass IPv4 traffic from the local network to the internet
pass in on $int_if inet proto tcp from $int_if:network to any port $tcpworkstationsout
pass in on $int_if inet proto udp from $int_if:network to any port $udpworkstationsout
# Pass IPv6 traffic from the local network to the internet
pass in on $int_if inet6 proto tcp from $int_if:network to any port $tcpworkstationsout
pass in on $int_if inet6 proto udp from $int_if:network to any port $udpworkstationsout

# Allow ICMP types neccessary for MTU path discovery to work:
pass inet proto icmp all icmp-type unreach code needfrag

# Allow ICMP echo requests
icmp_types = "{ echoreq, unreach }"
pass inet proto icmp all icmp-type $icmp_types

# Allow ICMP6
pass inet6 proto icmp6

# Permit all traffic going out on any interface.  
#   - we restrict packets on ingress to an interface not egress.
pass out inet
pass out inet6

# pass statements for altq qos
#   - qos can't work unless there is a pass rule to act upon
#   - these rules have to come *after* the 'pass out' rules above to have any effect
pass out on $ext_if inet proto udp from $ext_if to any port 53 queue (dns)
pass out on $ext_if inet proto tcp from $ext_if to any port 22 queue (ssh_bulk, ssh_login)
pass out on $ext_if inet proto tcp from $ext_if to any port 80 queue (web)
pass out on $ext_if inet proto tcp from $ext_if to any port 443 queue (web)
pass out on $ext_if inet proto tcp from $ext_if to any port 25 queue (mail)
pass out on $ext_if inet proto udp from $ext_if to any port 4569 queue (iax)
# IPv6 pass statements for altq qos
pass out on $ext_if inet6 proto udp from $ext_if to any port 53 queue (dns)
pass out on $ext_if inet6 proto tcp from $ext_if to any port 22 queue (ssh_bulk, ssh_login)
pass out on $ext_if inet6 proto tcp from $ext_if to any port 80 queue (web)
pass out on $ext_if inet6 proto tcp from $ext_if to any port 443 queue (web)
pass out on $ext_if inet6 proto tcp from $ext_if to any port 25 queue (mail)
pass out on $ext_if inet6 proto udp from $ext_if to any port 4569 queue (iax)

# Turn on antispoof
antispoof for $ext_if
antispoof for $int_if
antispoof for lo0

pfstat

Stats package for pfctl. I don't run pfstat on the firewall itself because it needs the xbase set, so I install pfstat on an OpenBSD virtual machine. If you don't have xbase, it can be added easily. Fetch the xbase46.tgz (or whatever corresponds to the version of OpenBSD you are running) set from an OpenBSD mirror and place it in the root of your filesystem. Then:

vm# cd /
vm# tar xzvphf xbase46.tgz
vm# ldconfig -m /usr/X11R6/lib

pfstat can be installed with pkg_add (dont forget to set $PKG_PATH if it's a new system):

vm# pkg_add -r pfstat

On the firewall itself, add just the pfstatd daemon (which doesn't need the xbase set):

firewall# pkg_add -r pfstatd

Start pfstatd and add it to rc.conf.local (where 192.168.0.254 is your firewall's internal IP address):

firewall# pfstatd -a 192.168.0.254
firewall# echo "pfstatd_flags="-a 172.16.0.254"" >> /etc/rc.conf.local

Finally, place a loginterface directive in pf.conf so that pfstatd has something to look at:

set loginterface vr0
fw# pfctl -f /etc/pf.conf

pfstatd just trundles away monitoring /dev/pf and sets up a listener on tcp 9999. pfstat connects to this and performs the analysis as normal. Let's do that. Here's a simple pfstat.conf:

vm# vi /etc/pfstat.conf

#  ATTN! REPLACE 'vr0' WITH YOUR EXTERNAL INTERFACE!
#     e.g :%s/vr0/vic0/g

#
# Bytes in and out per second and states (connections)
# per second
#

collect 1 = interface "vr0" pass bytes in ipv4 diff
collect 2 = interface "vr0" pass bytes out ipv4 diff
collect 3 = global states entries

image "/var/www/htdocs/pfstat/pfstat_day.png" {
        from 1 days to now
        type png
        width 1200 height 350
        left
                graph 1 "in" "bytes/s" color 0 192 0 filled,
                graph 2 "out" "bytes/s" color 0 0 255
        right
                graph 3 "states" "entries" color 192 192 0
}

image "/var/www/htdocs/pfstat/pfstat_week.png" {
        from 1 weeks to now
        type png
        width 1200 height 350
        left
                graph 1 "in" "bytes/s" color 0 192 0 filled,
                graph 2 "out" "bytes/s" color 0 0 255
        right
                graph 3 "states" "entries" color 192 192 0
}

image "/var/www/htdocs/pfstat/pfstat_month.png" {
        from 1 months to now
        type png
        width 1200 height 350
        left
                graph 1 "in" "bytes/s" color 0 192 0 filled,
                graph 2 "out" "bytes/s" color 0 0 255
        right
                graph 3 "states" "entries" color 192 192 0
}

#
#  Matches against pass/block statements per second
#

collect 4 = interface "vr0" pass packets in ipv4 diff
collect 5 = interface "vr0" pass packets out ipv4 diff
collect 6 = interface "vr0" block packets in ipv4 diff
collect 7 = interface "vr0" block packets out ipv4 diff

image "/var/www/htdocs/pfstat/pfstat-packets_day.png" {
        from 1 days to now
        type png
        width 1200 height 350
        left
                graph 4 "pass in"   "packets/s" color 0 192 0 filled,
                graph 5 "pass out"  "packets/s" color 0 0 255
        right
                graph 6 "block in"  "packets/s" color 255 0 0,
                graph 7 "block out" "packets/s" color 192 192 0
}

image "/var/www/htdocs/pfstat/pfstat-packets_week.png" {
        from 1 weeks to now
        type png
        width 1200 height 350
        left
                graph 4 "pass in"   "packets/s" color 0 192 0 filled,
                graph 5 "pass out"  "packets/s" color 0 0 255
        right
                graph 6 "block in"  "packets/s" color 255 0 0,
                graph 7 "block out" "packets/s" color 192 192 0
}

image "/var/www/htdocs/pfstat/pfstat-packets_month.png" {
        from 1 months to now
        type png
        width 1200 height 350
        left
                graph 4 "pass in"   "packets/s" color 0 192 0 filled,
                graph 5 "pass out"  "packets/s" color 0 0 255
        right
                graph 6 "block in"  "packets/s" color 255 0 0,
                graph 7 "block out" "packets/s" color 192 192 0
}

Finally, we can generate some graphs:

vm# mkdir -p /var/www/htdocs/pfstat
vm# pfstat -q -d /var/db/pfstat.db -r 192.168.0.254
vm# pfstat -p -d /var/db/pfstat.db

You should see a load of graphs in the directory you created:

vm# ls -l /var/www/htdocs/pfstat/                                                                 
total 204
-rw-r--r--  1 root  daemon  8268 Dec  8 10:00 pfstat-packets_day.png
-rw-r--r--  1 root  daemon  4710 Dec  8 10:00 pfstat-packets_month.png
-rw-r--r--  1 root  daemon  4368 Dec  8 10:00 pfstat-packets_week.png
-rw-r--r--  1 root  daemon  8921 Dec  8 10:00 pfstat_day.png
-rw-r--r--  1 root  daemon  5049 Dec  8 10:00 pfstat_month.png
-rw-r--r--  1 root  daemon  4549 Dec  8 10:00 pfstat_week.png

Assuming that this was successful, you'll need to re-generate these graphs periodically. I do this with cron:

# Query pfstatd every minute
*       *       *       *       *       /usr/local/bin/pfstat -q -d /var/db/pfstat.db -r 192.168.0.254
# Use the collected data to draw graphs every 5 minutes
*/5     *       *       *       *       /usr/local/bin/pfstat -p -d /var/db/pfstat.db
# Once per day remove any data older than 30 days
25      3       *       *       *       /usr/local/bin/pfstat -t 30 -d /var/db/pfstat.db

You'll probably want the facility to view your graphs. Conveniently, Apache is part of the base install in OpenBSD.

vm# vi /var/www/htdocs/pfstat/index.html

<html>
<body>
<h2>Bytes Per Second and Connections Tracked Per Second in the Past 24 Hours</h2>
<img src="pfstat_day.png" />
<h2>Bytes Per Second and Connections Tracked Per Second in the Past Week</h2>
<img src="pfstat_week.png" />
<h2>Bytes Per Second and Connections Tracked Per Second in the Past Month</h2>
<img src="pfstat_month.png" />
<h2>Passed/Blocked Packets Per Second in the Past 24 Hours</h2>
<img src="pfstat-packets_day.png" />
<h2>Passed/Blocked Packets Per Second in the Past Week</h2>
<img src="pfstat-packets_week.png" />
<h2>Passed/Blocked Packets Per Second in the Past Month</h2>
<img src="pfstat-packets_month.png" />
</body>
</html>

Start Apache:

vm# echo "httpd_flags=\"\"" >> /etc/rc.conf.local
vm# apachectl start

Browse to http://vm/pfstat/ and you'll see graphs.