# PF firewall rules issue



## Ethin (Aug 2, 2018)

I've got some PF rules to define a table for persistent storage in a file to store IP addresses I want to block and some other rules, but its not working. My PF configuration is below:

```
table <blockedips> persist file "/etc/pfblocked.conf"
block on vtnet0 from <blockedips> to any
antispoof log for vtnet0 inet
antispoof log for vtnet0 inet6
pass in on vtnet0 all
pass out on vtnet0 all
pass in on lo0 all
pass out on lo0 all
table <fail2ban> persist
block in quick from <fail2ban>
```
Without locking myself out, would the following work?

```
table <blockedips> persist file "/etc/pfblocked.conf"
block in quick from <blockedips> to any
antispoof log for vtnet0 inet
antispoof log for vtnet0 inet6
pass in on vtnet0 all
pass out on vtnet0 all
pass in on lo0 all
pass out on lo0 all
table <fail2ban> persist
block in quick from <fail2ban>
```


----------



## ShelLuser (Aug 2, 2018)

Please define "not working", what errors do you get or what behavior did you expect?

"it doesn't work" isn't a good way to ask questions.

Also: "_without locking myself out_". How do you expect us to answer that when you shared 0 details about your network setup? I don't know from which IP you're operating, I don't even know what IP addresses are in the table...

Sorry, but this is impossible to answer. You're not giving us enough information to do so.

(edit)

I would suggest taking a good look at pf.conf(5) because PF is very picky about the sequence of the rules in a config file. That's the #1 issue people new to PF run into.


----------



## Ethin (Aug 2, 2018)

I apologize about that. I use a digital ocean server that hosts all my stuff, so its a VPS, running FreeBSD 11.2-release. "Without locking myself out" means preventing me from SSHing into the server. I've got multiple IP ranges in the blocklist. What I mean by "it doesn't work" is that when I add an IP to the table, it doesn't actually get blocked. As an example, if I want to block the IP 124.212.220.21 because its a bot, and I add 124.212.220.21 to the table, it still is able to connect to services (i.e. ssh). What I want to happen is: when I add an IP to the table, I want it to be refused on all ports on the server.
HTH


----------



## ShelLuser (Aug 3, 2018)

Short story made longer: your rules setup is a total mess, you really need to pay better attention to the documentation.

For starters: rules get processed in sequence. And by default the firewall checks a rule and then continues to check if something else would match too. That's what's causing your problems:


Ethin said:


> ```
> table <blockedips> persist file "/etc/pfblocked.conf"
> block on vtnet0 from <blockedips> to any
> antispoof log for vtnet0 inet
> ...


First you tell the firewall that it should block all IP's within the blockedips table. But then, a few rules later, you tell the system: `pas in on vtnet0 all`. So yeah, obviously it doesn't block anything from the table because you told the firewall to pass everything a few rules later. That should honestly be basic knowledge...  From pf.conf(5):

```
For each packet processed by the packet filter, the filter rules are
     evaluated in sequential order, from first to last.  The last matching
     rule decides what action is taken.  If no rule matches the packet, the
     default action is to pass the packet.
```

Anyway, this might work:

```
ext_if = "vtnet0"
me = "127.12.13.14" # replace with your real IP address

table <blockedips> persist file "/etc/pfblocked.conf"
table <fail2ban> persist

set block-policy drop
set skip on lo0

pass in quick on $ext_if from $me to any port ssh

block in quick from <blockedips>
block in quick from <fail2ban>
```
If you don't know your own IP address then that still wouldn't matter too much, unless you suddenly find yourself listed on one of those tables. After all: all data gets accepted except when it originates from an IP within those tables.

Note: this is an optimized version of what you're using above, it's most definitely not something I'd use or even recommend using because it doesn't keep you safe. _Far_ from it. You're basically providing every service you run on that box to the Internet and only if you happen to catch someone performing foul play will you add him to your blocking tables. Here's hoping you didn't miss someone, which is very likely.

The real danger doesn't come from people trying to log in. It comes from people who are aware of exploitable bugs in the services you run and then exploit those bugs. Trust me: that's the stuff you _won't_ find in your logfiles.

So, what I would suggest is this:


```
ext_if = "vtnet0"
me = "127.12.13.14" # replace with your real IP address

table <blockedips> persist file "/etc/pfblocked.conf"
table <fail2ban> persist

set block-policy drop
set skip on lo0

# Personal failsave
pass in quick on $ext_if from $me to any port ssh

block in quick from <blockedips>
block in quick from <fail2ban>

block in on $ext_if
pass out quick on $ext_if keep state

# SSH failsave
pass in quick on $ext_if proto {tcp, udp} from any to port 5327
```
But before you enable this ruleset be sure to reconfigure /etc/ssh/sshd_config and tune it to also use port 5327, as mentioned in the rules above. Of course: better yet is to use another random port.

If you value your security I'd also combine this with key based authentication for SSH. Disable PAM and passwords entirely, this will prevent people from being able to guess.

I would also advice you to look into blacklistd(8) and maybe replace Fail2Ban with this critter. Nothing negative about Fail2Ban but it only works after the facts. It goes over your logs and stuff and only runs every once in a while when the cron tells it to. blacklistd on the other hand constantly runs. Even better: it will respond to signals sent to it by the several services. SSH in the base system for example is perfectly capable of telling blacklistd that someone tried to log in but failed. As a result they would get blocked immediately. Not when the next Fail2Ban cycle runs, after 15 or so minutes (dramatic assumption on my end ). Also: blacklistd is part of the FreeBSD base system, so you probably already have it, maybe without you knowing.

Anyway... about the ruleset:

It blocks everything but allows for other rules to be parsed. So unless you add a 'pass' rule then stuff gets blocked. It quickly passes outgoing data and enables stateful filtering. In short: make sure returning data passes through the firewall.

And one rule... is to allow incoming traffic on that weird port, which is meant for SSHd to listen on. You can continue building onto that with other services if you need.


----------



## Ethin (Aug 3, 2018)

Thank you for your tips. I'll implement the second ruleset, as suggested, and allow all the ports I need opened (though digital ocean cloud firewalls will also take care of this). I didn't know they were that bad; guess I don't understand PF as well as I thought.


----------



## SirDice (Aug 3, 2018)

Ethin said:


> I use a digital ocean server that hosts all my stuff, so its a VPS, running FreeBSD 11.2-release. "Without locking myself out" means preventing me from SSHing into the server.


A tip from the trenches. Save a good rule set (that allows you access) as /etc/pf.conf.backup. Then test your new rules like this:
`pfctl -f /etc/pf.conf.new && sleep 30 && pfctl -f /etc/pf.conf.backup`

This will load your pf.conf.new ruleset, sleep for 30 seconds, then load the pf.conf.backup set. That will give you about 30 seconds to run some quick tests, and the backup is automatically restored. So if you lock yourself out you will regain control again after those 30 seconds.


----------



## sko (Aug 3, 2018)

SirDice said:


> A tip from the trenches. Save a good rule set (that allows you access) as /etc/pf.conf.backup. Then test your new rules like this:
> pfctl -f /etc/pf.conf.new && sleep 30 && pfctl -f /etc/pf.conf.backup


+1

I use exactly this in my workflow with PF on almost all of my hosts (at least remote and/or production).
Actually I defined an alias in my .cshrc to test a new pf configuration:

```
alias pftest  doas pfctl -f /etc/pf/pf.new && sleep 60 && doas service pf restart"
```

I completely restart pf after the 60s grace period to flush all states and tables that might have been borked by a badly written rule (this has bitten me more than once, especially with NAT...). If everything works as expected within the 60 second period, I just abort with `^c` and the new ruleset is kept.

/etc/pf.conf on those hosts is a _very_ minimal and rather restrictive ruleset for emergency cases; the actual production ruleset resides in /etc/pf/pf.conf and in /etc/rc.conf.local I set `pf_rules="/etc/pf/pf.conf"` to point to that ruleset.
The /etc/pf.conf is just kept for worst-case events (can't remember when I actually needed it on any host...)

The /etc/pf directory is a git repository and holds the current (pf.conf) and new (pf.new) ruleset and all files and tables that are included by the ruleset.
Configuration changes go to pf.new (in small steps!); are being tested/applied with `pftest` and if they work as inteded, they get commited with an appropriate comment. If all work is finished and tested, the changes to pf.new are applied to pf.conf and the change is commited as "apply new configuration". All change history is tied to pf.new, not scattered between both files (important when reviewing/troubleshooting a ruleset that has been growing for a while)
A nice bonus when using git: you can use branches for special cases or tests. E.g. during maintenance, upgrades (jails!) or backup/recovery you can just switch to another branch with rules (preferably in included files) to allow e.g. ftp traffic that you don't want/need during normal operation.


Regarding DigitalOcean:
You really can't lock yourself completely out of a droplet if you have at least one user account with a known password. Just access the droplet via the web console and fix what is broken. I had to do this several times while building my customized droplet template (all cloudinit/DO-specific cruft removed that nuked my network configuration and other settings on a regular basis...) and it works pretty well. Increase the `autoboot_delay` in /boot/loader.conf.local to be able to access the boot loader menu, as it takes a few seconds for the control panel to attach to a newly (re)started droplet.


----------



## Ethin (Aug 3, 2018)

sko said:


> +1
> 
> I use exactly this in my workflow with PF on almost all of my hosts (at least remote and/or production).
> Actually I defined an alias in my .cshrc to test a new pf configuration:
> ...


I would've used the web console. I'm visually impaired so, so the digital ocean console doesn't work with my screen reader. (And it doesn't seem to like any of my web browsers, either.) So, I would be pretty much locked out.  That git repository sounds useful for a lot of things. I' using ZFS.... too bad ZFS can't be used that way. Well, technically it can with snapshots, but you'd have to give the snapshots a long name to know what they're used for.


----------



## sko (Aug 7, 2018)

Ethin said:


> I' using ZFS.... too bad ZFS can't be used that way. Well, technically it can with snapshots, but you'd have to give the snapshots a long name to know what they're used for.



ZFS snapshots aren't meant for revision-control. However, snapshots are invaluable when you've completely borked PF or some other service, so you can just roll back in time to a known-good state or just recover the config file(s) from some earlier point in time.
I'm running zfSnap in varying intervals and various hold-times on all of my systems. Additionally I'm using boot environments with sysutils/beadm for updates or major configuration changes (i.e. installing a new service). But again: ZFS snapshots are not meant and therefore aren't suited at all to be abused for revision control; they are more of a safety net and additional (fast and easily accessible) layer before backups.


----------



## Lamia (Aug 7, 2018)

sko said:


> alias pftest  doas pfctl -f /etc/pf/pf.new && sleep 60 && doas service pf restart"


As much as I like that line, it annoys me by having it run every time I reboot & login because it is in the .cshrc file.

I have now make it a shell script I can call with /bin/sh. I prefer it as an alias though.


----------

