On a workstation where you are the only user, you can use a very simple /etc/pf.conf:
set skip on lo0 # don't filter localhost packets
ext_if = "em0" # replace em0 with your external interface
set block-policy drop # by default, drop packets. You can also set block-policy reject
set loginterface $ext_if # log that interface
block all # block all traffic by default
pass in inet proto icmp icmp-type 8 code 0 # icmp packets
pass in inet proto icmp icmp-type 3 code 4 # icmp needfrag (MTU)
pass in inet6 proto ipv6-icmp icmp6-type {2 128} keep state
pass out all # pass all outgoing traffic
This will allow the necessary ICMP traffic (useful for network diagnosis) while blocking all other incoming connections.
(As a general rule, the last matching rule determines the action.)
I generally don't whitelist by IP addresses because I've had times where I needed to access a system from a different IP. I also avoid OS fingerprinting because, although it is available, it's not 100% accurate.
To load the ruleset once you've edited it, run:
$ doas pfctl -f /etc/pf.conf
To disable the firewall (useful for diagnosing the network), run:
$ doas pfctl -d
To enable it again:
$ doas pfctl -e
For a server, you will want to, at a minimum, allow incoming ssh packets:
set skip on lo0 # don't filter localhost packets
ext_if = "em0" # my external interface is em0
set block-policy drop # by default, drop packets. You can also set block-policy reject
set loginterface $ext_if # log that interface
pass in proto tcp from 192.168.1.1 to port ssh
pass in inet proto icmp icmp-type 8 code 0 # icmp packets
pass in inet proto icmp icmp-type 3 code 4 # icmp needfrag (MTU)
pass in inet6 proto ipv6-icmp icmp6-type {2 128} keep state
pass out all # pass all outgoing traffic
Replace 192.168.1.1 with your IP.
As a general rule, your servers should also accept incoming http and https connections. This is necessary for running a web server and also for acquiring a properly signed SSL certificate. Here is the /etc/pf.conf:
set skip on lo0 # don't filter localhost packets
ext_if = "em0" # my external interface is em0
set block-policy drop # by default, drop packets. You can also set block-policy reject
set loginterface $ext_if # log that interface
pass in proto tcp from 192.168.1.1 to port ssh
pass in inet proto icmp icmp-type 8 code 0 # icmp packets
pass in inet proto icmp icmp-type 3 code 4 # icmp needfrag (MTU)
pass in inet6 proto ipv6-icmp icmp6-type {2 128} keep state
pass in proto tcp to port {http https}
pass out all # pass all outgoing traffic