Pf firewall notes

From Wiki
Revision as of 23:08, 10 February 2011 by Stocksy (Talk | contribs) (pf.conf)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
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 -n -v -f /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 -e -f /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 a sample configuration for a firewall with three interfaces: A LAN, an Internet connection and a Voice network. Valid for OpenBSD version 4.6. The syntax for some of the NAT rules has changed in 4.7 or 4.8, so look out for that.

Packets are matched against the last rule encountered unless the quick option is used. Specifying quick for everything would turn it into a first-match-wins firewall.

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

### Macros

## Interfaces
# Be ADSL internet feed
ext_if="vr0"
# Inside network
int_if="vr1"
# Voice network
voi_if="vr2"
# The PTP interface for the he.net IPv6 tunnel
6_if="gif0"


## Hosts
# Internal
www2_alt2_v4_int = "192.168.0.212"
mbp_v4_int = "192.168.0.44"
www2_alt1_v4_int = "192.168.0.120"
ns1_v4_int = "192.168.0.104"
ns1_alt1_v4_int = "192.168.0.145"
proxy1_v4_int = "192.168.0.127"
mx_v4_int = "192.168.0.200"
www_v4_int ="192.168.0.211"
ns2_v4_int = "192.168.0.225"
www2_v4_int = "192.168.0.128"
torrent_v4_int = "192.168.0.229"
lc475_v4_int = "192.168.0.182"
fw_v4_int = "192.168.0.254"
pabx_v4_voi = "192.168.1.22"


# IPv4 External
gen_nat1_v4_ext = "78.105.x.x"
mx_v4_ext = "78.105.x.x"
www2_v4_ext = "78.105.x.x"
www_v4_ext = "78.105.x.x"
lc475_v4_ext = "78.105.x.x"
pabx_v4_ext = "78.105.x.x"

# IPv6
mbp_v6_spruce = "2001:470:903f::xxx"
www2_v6_spruce = "2001:470:903f::xxx"
lc475_v6_spruce = "2001:470:903f::xxx"
mx_v6_spruce = "2001:470:903f::xxx"
ns1_v6_spruce = "2001:470:903f::xxx"
ns2_v6_spruce = "2001:470:903f::xxx"

## Networks
spruce_v6 = "2001:470:903f::/48"

## Port groups
web_pg = "{ http, https }"
sprucehosts_tcp_pg = "{ ssh, http, https }" 
sprucehosts_udp_pg = "{ ntp, 5999 }"
iax_pg = 4569
smtp_pg = "{ 25, 587 }"
bittor_pg = "6881:6999"


### End Macros

set skip on lo0
set loginterface $ext_if
set loginterface $6_if

### QOS
# Queue for uploads on the Be ADSL link - gets saturated at about 950kb, so backed off to 900kb
 altq on $ext_if bandwidth 900Kb hfsc queue { iax, ack, dns, ssh, web, ip6, 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 0 hfsc (realtime 128Kb)
  queue dns        bandwidth  5% priority 7 qlimit 500 hfsc (realtime    2%)
  queue ssh        bandwidth 10% 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 web        bandwidth  5% priority 5 qlimit 500 hfsc (realtime  (5%, 2000, 2%) )
  queue ip6	   bandwidth  5% priority 5 qlimit 500 hfsc (realtime    5%)
  queue bulk       bandwidth 20% priority 4 qlimit 500 hfsc (realtime   2% default)
  queue mail       bandwidth  5% priority 3 qlimit 500 hfsc
  # bittorrent can never exceed 50% of outgoing bandwidth capacity
  queue bittor     bandwidth  1% priority 2 qlimit 500 hfsc (upperlimit 50%)

# Queue for downloads on Be ADSL link.  Tops out at around 7Mb.
# Set even lower than usual to guarantee a little bandwidth is available for voice network $voi_if
altq on $int_if bandwidth 6.5Mb hfsc queue { ack_$int_if, ssh_$int_if, ip6_$int_if, web_$int_if, bulk_$int_if, mail_$int_if, dns_$int_if, bittor_$int_if }
 queue ack_$int_if  bandwidth 1Mb priority 8 qlimit 500 hfsc (realtime 0.5Mb)
 # can't put iax here because it leaves on $voi_if
 queue ssh_$int_if  bandwidth 1Mb priority 5 qlimit 500 hfsc (realtime 0.5Mb) {ssh_login_$int_if, ssh_bulk_$int_if }
  queue ssh_login_$int_if    bandwidth 50% priority 5 qlimit 500 hfsc
  queue ssh_bulk_$int_if     bandwidth 50% priority 4 qlimit 500 hfsc
 queue web_$int_if  bandwidth 1Mb priority 4 qlimit 500 hfsc (realtime 0.5Mb)
 queue dns_$int_if  bandwidth 0.25Mb priority 6 qlimit 500 hfsc (realtime 0.25Mb)
 queue ip6_$int_if  bandwidth 1Mb priority 3 qlimit 500 hfsc (realtime 0.5Mb)
 queue bulk_$int_if bandwidth 1Mb priority 3 qlimit 500 hfsc (realtime 0.5Mb default)
 queue mail_$int_if bandwidth 0.5Mb priority 2 qlimit 500 hfsc (realtime 0.25Mb)
 queue bittor_$int_if bandwidth  0.1Mb priority 1 qlimit 500 hfsc (upperlimit 4Mb)

### END QOS

### FTP stuff
# Proxy FTP connections for users on the internal network
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

### END FTP

### NAT rules

## rdr (redirect) rules are the port forwarding rules 
rdr on $ext_if inet proto tcp from any to $gen_nat1_v4_ext port $bittor_pg -> $torrent_v4_int 
rdr on $ext_if inet proto { tcp, udp }  from any to $gen_nat1_v4_ext port 50000 -> $torrent_v4_int
rdr on $ext_if inet proto { tcp, udp } from any to $gen_nat1_v4_ext port domain -> $ns1_v4_int
rdr on $ext_if inet proto tcp from any to $gen_nat1_v4_ext port 80 -> $www_v4_int

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

## These are the 'static' 1:1 NAT rules
binat on $ext_if from $lc475_v4_int to any -> $lc475_v4_ext 
binat on $ext_if from $mx_v4_int to any -> $mx_v4_ext
binat on $ext_if from $www2_alt1_v4_int to any -> $www2_v4_ext
binat on $ext_if from $www_v4_int to any -> $www_v4_ext
binat on $ext_if from $pabx_v4_voi to any -> $pabx_v4_ext

### END NAT

### pass and block statements; connections we want
# If we don't specify otherwise later, block it and log it 
block log all

## Use an FTP proxy on this box
anchor "ftp-proxy/*"
pass in on $int_if proto tcp from $int_if:network to lo0 port 8021

## IPv4 connections through to internal hosts
# Internal LAN
pass in on $ext_if inet proto tcp from any to $mx_v4_int port imaps
pass in on $ext_if inet proto tcp from any to $mx_v4_int port $smtp_pg
pass in on $ext_if inet proto { tcp, udp } from any to $ns1_v4_int port domain
pass in on $ext_if inet proto { tcp, udp } from any to $ns2_v4_int port domain
pass in on $ext_if inet proto tcp from any to $www2_alt1_v4_int port $web_pg
pass in on $ext_if inet proto tcp from any to $www_v4_int port $web_pg
pass in on $ext_if inet proto tcp from any to $lc475_v4_int port $web_pg

## Voice rules
pass in on $voi_if inet proto udp from $pabx_v4_voi to { !$voi_if, !$int_if:network } port 4569
pass in on $voi_if inet proto { tcp, udp } from $voi_if:network to { $ns2_v4_int, $ns1_alt1_v4_int } port domain 
# DHCP from voice network
pass in on $voi_if inet proto { tcp, udp } from any to any port { 67, 68 }
pass in on $voi_if inet proto tcp from $pabx_v4_voi to $mx_v4_int port $smtp_pg
pass in on $voi_if inet proto tcp from $pabx_v4_voi to $proxy1_v4_int port { 8080, 3128 } 

## IPv6 connections through to internal hosts
pass in on $6_if inet6 proto tcp from any to $www2_v6_spruce port $web_pg
pass in on $6_if inet6 proto tcp from any to $lc475_v6_spruce port $web_pg
pass in on $6_if inet6 proto tcp from any to $mx_v6_spruce port $smtp_pg
pass in on $6_if inet6 proto tcp from any to $mx_v6_spruce port imaps 
pass in on $6_if inet6 proto { tcp, udp } from any to { $ns2_v6_spruce, $ns1_v6_spruce } port domain

## connections to the firewall  
# IPv6 tunnel
pass in on $ext_if inet proto 41 from any to $gen_nat1_v4_ext

## Pass IPv4 and IPv6 traffic from the local network to elsewhere
pass in on $int_if inet proto tcp from $int_if:network to !$int_if port $sprucehosts_tcp_pg
pass in on $int_if inet proto udp from $int_if:network to !$int_if port $sprucehosts_udp_pg
pass in on $int_if inet6 proto tcp from $int_if:network to !$int_if port $sprucehosts_tcp_pg
pass in on $int_if inet6 proto udp from $int_if:network to !$int_if port $sprucehosts_udp_pg
pass in on $int_if inet proto tcp from $torrent_v4_int to !$int_if port $bittor_pg
# Due to !$int_if, need to explicitly allow ssh to fw even though it is allowed 'out'.
pass in on $int_if inet proto tcp from $int_if:network to $int_if port ssh

# Permit all traffic going out of an interface (not the router as a whole)
pass out inet
pass out inet6

# 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
pass quick proto icmp6 all

## pass out statements for altq qos
## these pass out statements do nothing in terms of security because of the 
## above pass out inet/inet6 rule; they just tag traffic for the QoS rules

## Upstream traffic - this is to 'any' because all traffic must be shaped
# ACK
pass out on $ext_if inet proto tcp from ($ext_if) to any flags S/SA modulate state queue (bulk, ack)
# Can't be more granular because it's a tunnel
pass out on $ext_if inet proto 41 from ($ext_if) to any queue (ip6) 
pass out on $ext_if inet proto tcp from ($ext_if) to port ssh queue (ssh_bulk, ssh_login)
pass out on $ext_if inet proto tcp from ($ext_if) to any port $web_pg queue (web)
pass out on $ext_if inet proto tcp from ($ext_if) to any port $smtp_pg queue (mail)
pass out on $ext_if inet proto udp from ($ext_if) to any port $iax_pg queue (iax)
pass out on $ext_if inet proto { udp, tcp } from ($ext_if) to any port domain queue (dns)
pass out on $ext_if inet from $torrent_v4_int to any queue (bittor)

## Downstream traffic is shaped going out of the $int_if, because it must be so.
## From !$voi_if:network because traffic from one LAN to another has plenty
## of bandwidth available

pass out on $int_if inet proto tcp from !$voi_if:network to any flags S/SA modulate state queue (bulk_$int_if, ack_$int_if)
pass out on $int_if inet proto tcp from !$voi_if:network to any port ssh queue (ssh_$int_if)
pass out on $int_if inet proto tcp from !$voi_if:network to any port $web_pg queue (web_$int_if)
pass out on $int_if inet proto tcp from !$voi_if:network to any port $smtp_pg queue (mail_$int_if)
pass out on $int_if inet proto udp from !$voi_if:network to any port domain queue (dns_$int_if)
pass out on $int_if inet from any to $torrent_v4_int queue (bittor_$int_if)
pass out on $int_if inet6 from any to any queue (ip6_$int_if)

### END pass and block rules

antispoof for $ext_if
antispoof for $int_if
antispoof for $voi_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 192.168.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.