PF My first ruleset; what am I doing wrong?

Hi,

I'm learning to set up a secure FreeBSD server (10.2) and I think I figured it all excepted for PF.

Here is my current ruleset:

Code:
tcp_services = "{ ssh }"
udp_services = "{ ntp }"
icmp_types = "echoreq"
block all
pass out proto tcp to any port $tcp_services keep state
pass proto udp to any port $udp_services keep state
pass inet proto icmp all icmp-type $icmp_types keep state

As you can see, nothing fancy.

I have macros for tcp (ssh for now) and udp (ntp) for now. When needed, I will add other protocols (mainly https, I guess), but for now, I keep it as simple as possible and do by the (hand)book.

There is also a macro for icmp since my host ping the machine to automatically check if it's OK. That macro seems to work, at least I can ping the server and I don't receive any automatic mail from the host.

But now, I can't connect through SSH, so I guess there is a problem with my SSH line, but I can't see where I am wrong. Any hint would be greatly appreciated.
 
But now, I can't connect through SSH, so I guess there is a problem with my SSH line, but I can't see where I am wrong. Any hint would be greatly appreciated.
Your ruleset only allows outgoing connections to port 22, not incoming.

With firewall rules (on any type of firewall) it is very important to note the direction of traffic. Of course there will be data transferred back and forth (hence the need for keeping state) but the important bit is which side initiates it.
 
Thank you all for your kind and fast answers.

I updated (not tested yet) my rules:

Code:
# ee /etc/pf.conf
tcp_services = "{ ssh }"
udp_services = "{ ntp }"
icmp_types = "echoreq"
block all
pass proto tcp to any port $tcp_services
pass proto udp to any port $udp_services
pass inet proto icmp all icmp-type $icmp_types

Is that correct?
 
If you want to allow both incoming and outgoing connections, then yes.

Note, you do not need to specifically allow the 'return' traffic, that's what keep state does.
 
Thank you, that will do for now.
I just got The Book of PF that I'll read carefully to understand at least the needed basics. ^^
 
Not yet, you need to separate outgoing traffic from incoming. You should have separate rules for both directions and also specify the interface the rule affects, something like:

Code:
# Change to match the actual external interface
ext_if = em0

# Do not filter on loopback
set skip on lo0

block all

# Pass all outgoing traffic, tighten if filtering of outgoing traffic is desired.
pass out quick on $ext_if inet all

pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services

Note that I added quick on the rules to make the rule evaluation stop when a matching rule is met. The inet keywords are there to specify IPv4 so those rules would block all IPv6.
 
OK, so I updated my ruleset and added more comments to really get the grasp of it:

Code:
# Macros declaration
tcp_services = "{ ssh }"
udp_services = "{ ntp }"
icmp_types = "echoreq"

# Change to match the actual external interface
ext_if = em0

# Do not filter on loopback
set skip on lo0

block all

# Pass all outgoing traffic, tighten if filtering of outgoing traffic is desired.
pass out quick on $ext_if inet all

# Macros content
pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services
pass inet proto icmp all icmp-type $icmp_types

Thank you all for your help!
 
Um, got a problem here:

Code:
# service pf start
Enabling pfNo ALTQ support in kernel
ALTQ related functions disabled
no IP address found for em0
/etc/pf.conf:18: could not parse host specification
no IP address found for em0
/etc/pf.conf:19: could not parse host specification
pfctl: Syntax error in config file: pf rules not loaded

Code:
# ifconfig -a
re0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
    options=8209b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_MAGIC,LINKSTATE>
    ether e0:69:95:d1:ff:53
    inet 192.95.27.206 netmask 0xffffff00 broadcast 192.95.27.255
    inet6 fe80::e269:95ff:fed1:ff53%re0 prefixlen 64 scopeid 0x1
    inet6 2607:5300:60:2bce::1 prefixlen 128
    nd6 options=8023<PERFORMNUD,ACCEPT_RTADV,AUTO_LINKLOCAL,DEFAULTIF>
    media: Ethernet autoselect (100baseTX <full-duplex>)
    status: active
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
    options=600003<RXCSUM,TXCSUM,RXCSUM_IPV6,TXCSUM_IPV6>
    inet6 ::1 prefixlen 128
    inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
    inet 127.0.0.1 netmask 0xff000000
    nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>

So I changed em0 to re0 to match, but then, the ruleset loads and I am cut out of the server.
 
You shouldn't be doing the learning process of PF on a remote server that you don't have physical access to, do it on a VirtualBox VM running FreeBSD on your local machine.
 
I know, but since the remote has special default tweaks on it, what I do on VM is irrelevant on distant server, so although I did most of the test on a VM, without issue, when porting my process to the distant server, I had to rewrite almost everything.

Meanwhile, I narrowed down the problem: I was cut out from the server, but I can connect through ssh and all. So, there may be another problem underlying.

Update: Tried to reload rules, no problem. Rebooting the server: no problem, service is running, everything seems OK. I guess I should have rebooted the first time rather than activate the firewall using service pf start? Or maybe (re)load the rules first?
 
When remote rule editing is your only choice, a good tip is to set up crontab(1) to disable the firewall every few minutes or at least to load a known working ruleset that will grant you remote access. Also, you may find it useful to use pfctl(8) (something like pfctl -n newruleset) to parse your ruleset without actually loading it.
 
Trick I learned a long time ago: pfctl -f /etc/pf.conf.new && sleep 60 && pfctl -f /etc/pf.conf

If you lock yourself out with pf.conf.new, wait 60 seconds and the 'old' ruleset will be loaded. Adjust the sleep to give yourself enough time to test.
 
Yeah, I know about these, but I this server, which is on end of life, so each time I screw up, I just reinstall and by repeating the commands and all, I learn them and can try new things each time. Repeating the movement until it is perfect. ;-)
 
This is the latest rules I use.

Code:
ext_if = em0

tcp_services = "{ ssh smtp }"

udp_services = "{ ntp }"

icmp_types = "echoreq"

table <bruteforce> persist

block quick from <bruteforce>

pass quick proto { tcp, udp } from any to any port ssh \

    flags S/SA keep state \

    (max-src-conn 15, max-src-conn-rate 5/3, \

    overload <bruteforce> flush global)

set skip on lo0

antispoof for $ext_if

block all

pass out quick on $ext_if inet all

pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services keep state

pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services keep state

pass inet proto icmp all icmp-type $icmp_types

Once again, when loading them, the console hangs, so it seems I am doing something wrong somewhere (most likely, I screwed up the public key, but it may also be a rules problem, I am not sure yet).

Also, I deleted the "scrub in all", because it is constantly rejected.

I am pretty sure there is room for improvement, but that's the most I could do from the documentation and I would like to go as far as possible in terms of efficiency.

Before trolling on me, YES, I read the docs, official and unofficial, and it is because I don't find my answer in them that I am enquiring here, so please, if it isn't to really help me understand what point I am not understanding properly, just ignore the thread.
 
You can leave out keep state from your rules, it's the default anyway with PF if not specified. How are you reloading the rules? If you do # /etc/rc.d/pf restart or # service pf restart all existing states are flushed and the hang on the terminal is the expected result because you also killed your SSH connection to the system. Reload of rules without killing the existing states can be done by # service pf reload.
 
Oh, thank you very much, I like that way of doing things, the flushing, that's more secure.

I updated my code, both using your remarks and trying to get a better understanding also by myself. As such, I added some comments about the order of the rules. I think I put everything were it should be. I don't use any queueing or translation, but still left them as a memo; the more you read it, the more you remember it.

Code:
// Macros
ext_if = em0
tcp_services = "{ ssh smtp }"
udp_services = "{ ntp }"
icmp_types = "echoreq"

// Tables
table <bruteforce> persist
block quick from <bruteforce>
pass quick proto { tcp, udp } from any to any port ssh \
    flags S/SA keep state \
    (max-src-conn 15, max-src-conn-rate 5/3, \
    overload <bruteforce> flush global)

// Options
set skip on lo0
antispoof for $ext_if

// Traffic Normalization
scrub in all

// Queueing

// Translation

// Packet Filtering
block all
pass out quick on $ext_if inet all
pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services
pass inet proto icmp all icmp-type $icmp_types
 
For now, I changed it to:

Code:
# Macros
ext_if = em0
tcp_services = «{ ssh smtp }»
udp_services = «{ ntp }»
icmp_types = «echoreq»

# Tables
table <bruteforce> persist
block quick from <bruteforce>
pass quick proto { tcp, udp } from any to any port ssh \
        flags S/SA keep state \
        (max-src-conn 15, max-src-conn-rate 5/3, \
        overload <bruteforce> flush global)

# Options

# Traffic Normalization
set skip on lo0
antispoof for $ext_if

# Queueing

# Translation

# Packet Filtering
block all
pass out quick on $ext_if inet all
pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services
pass inet proto icmp all icmp-type $icmp_types

I put off "scrub in all" because whenever and wherever I put it, I get the following error:

Code:
Rules must be in order: options, normalization, queueing, translation, filtering

Where do I have to put it?
 
It's normalization, you can't have any filter or nat rules before it. Your antispoof rule in particular has to be placed after the scrub rule because it expands to filter rules.
 
In the end:

Code:
# Macros
ext_if = em0
tcp_services = «{ ssh smtp }»
udp_services = «{ ntp }»
icmp_types = «echoreq»

# Tables
table <bruteforce> persist
block quick from <bruteforce>
pass quick proto { tcp, udp } from any to any port ssh \
        flags S/SA keep state \
        (max-src-conn 15, max-src-conn-rate 5/3, \
        overload <bruteforce> flush global)

# Options

# Traffic Normalization
scrub in all
set skip on lo0
antispoof for $ext_if

# Queueing

# Translation

# Packet Filtering
block all
pass out quick on $ext_if inet all
pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services
pass inet proto icmp all icmp-type $icmp_types

Still got the error:

Code:
/etc/pf.conf:18: Rules must be in order: options, normalization, queueing, translation, filtering
 
Meanwhile:

Code:
# Macros
ext_if = em0
tcp_services = "{ ssh smtp }"
udp_services = "{ ntp }"
icmp_types = "echoreq"

# Tables
table <bruteforce> persist
block quick from <bruteforce>
pass quick proto { tcp, udp } from any to any port ssh \
        flags S/SA keep state \
        (max-src-conn 15, max-src-conn-rate 5/3, \
        overload <bruteforce> flush global)

# Options

# Traffic Normalization
scrub in all
set skip on lo0
antispoof for $ext_if

# Queueing

# Translation

# Packet Filtering
block all
pass out quick on $ext_if inet all
pass out quick on $ext_if inet6 all
pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet6 proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services
pass in quick on $ext_if inet6 proto udp from any to $ext_if port $udp_services
pass inet proto icmp all icmp-type $icmp_types

IPV6 support added.
Still has the scrub problem, though. :-(
 
Thank you, I updated it.
But I still don't understand where to put the scrub in all. The more I look the docs and tutorials, the more I think my BDS install is flawed, since it seems to work for everyone but me.

Code:
# Macros
ext_if = em0
tcp_services = "{ ssh smtp }"
udp_services = "{ ntp }"
icmp_types = "echoreq"

# Tables
table <bruteforce> persist
block quick from <bruteforce>
pass quick proto { tcp, udp } from any to any port ssh \
        flags S/SA keep state \
        (max-src-conn 15, max-src-conn-rate 5/3, \
        overload <bruteforce> flush global)

# Options
set skip on lo0

# Traffic Normalization
antispoof for $ext_if

# Queueing

# Translation

# Packet Filtering
block all
pass out quick on $ext_if inet all
pass out quick on $ext_if inet6 all
pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet6 proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services
pass in quick on $ext_if inet6 proto udp from any to $ext_if port $udp_services
pass inet proto icmp all icmp-type $icmp_types
 
You're refusing to understand that the scrub rule's place is in "Traffic Normalization" as per the documentation, the antispoof rule you now have there should be in "Packet Filtering".
 
It is not that I refuse, it is just that I fail to understand you.
Given your last sentence, I should have that:

Code:
# Macros
ext_if = em0
tcp_services = "{ ssh smtp }"
udp_services = "{ ntp }"
icmp_types = "echoreq"

# Tables
table <bruteforce> persist
block quick from <bruteforce>
pass quick proto { tcp, udp } from any to any port ssh \
        flags S/SA keep state \
        (max-src-conn 15, max-src-conn-rate 5/3, \
        overload <bruteforce> flush global)

# Options
set skip on lo0

# Traffic Normalization
scrub in all

# Queueing

# Translation

# Packet Filtering
antispoof for $ext_if
block all
pass out quick on $ext_if inet all
pass out quick on $ext_if inet6 all
pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet6 proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services
pass in quick on $ext_if inet6 proto udp from any to $ext_if port $udp_services
pass inet proto icmp all icmp-type $icmp_types

Still the same error.

Maybe we could gain time if you just pasted here a complete and corrected version of my rules, so that I can compare and finally get to understand. English is not my primary language and I am afraid I am not fluent enough to make the best use of your help the way we are doing it right now. :-(
 
Well I'm not the professional, but from my recent understanding of pf, I would try this way:

Code:
# Macros
ext_if = em0
tcp_services = "{ ssh smtp }"
udp_services = "{ ntp }"
icmp_types = "echoreq"

# Tables
table <bruteforce> persist

# Options
set skip on lo0

# Traffic Normalization
scrub in all

# Queueing

# Translation

# Packet Filtering
antispoof for $ext_if

block quick from <bruteforce>
pass quick proto { tcp, udp } from any to any port ssh \
        flags S/SA keep state \
        (max-src-conn 15, max-src-conn-rate 5/3, \
        overload <bruteforce> flush global)

block all
pass out quick on $ext_if inet all
pass out quick on $ext_if inet6 all
pass in quick on $ext_if inet proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet6 proto tcp from any to $ext_if port $tcp_services
pass in quick on $ext_if inet proto udp from any to $ext_if port $udp_services
pass in quick on $ext_if inet6 proto udp from any to $ext_if port $udp_services
pass inet proto icmp all icmp-type $icmp_types

Because I think the block and pass rules against bruteforce on ssh appeared too early in your file. As far as I understand, anything pf finds written after any pass or block rule must belong to packet filtering (so nothing belonging to options, normalization, queueing, translation can show up after any line starting with pass or block).
 
Back
Top