This is the 8th post for the NetBSD desktop tips series

About blacklistd(8)

blacklistd(8) provides an API that can be used by network daemons to communicate with a packet filter via a daemon to enforce opening and closing ports dynamically based on policy.

The interface to the packet filter is in /libexec/blacklistd-helper (this is currently designed for npf) and the configuration file (inspired from inetd.conf) is in etc/blacklistd.conf

Kernel SecureLevel

As we're likely planning to expose our workstation to Internet, it is advisable to raise the kernel securelevel at least to 1 - Secure mode (default= -1, Permanently insecure mode)... please refer to secmodel_securelevel(9).

Let's enable secure mode by specifying the securelevel=n variable inside /etc/rc.conf/; this will let init(8) take care of it upon boot.

$ sed -i '/hostname/a \ securelevel=1" ' /etc/rc.conf

At next boot we should see:

$ sysctl kern.securelevel

kern.securelevel = 1

Enabling NPF, bpfjit and npflog in secure mode

Among the other restrictions securelevel=1 implies:

Kernel modules may not be loaded or unloaded.

Adding or removing sysctl(9) nodes is denied.

Now, blacklistd(8) will require bpfjit(4) (Just-In-Time compiler for Berkeley Packet Filter) in order to properly work, in addition to, naturally, npf(7) as frontend and syslogd(8), as a backend to print diagnostic messages. Also remember npf shall rely on the npflog* virtual network interface to provide logging for tcpdump() to use.

Unfortunately (dont' ask me why 😛) in 8.1 all the required kernel components are still not compiled by default in the GENERIC kernel (though they are in HEAD), and are rather provided as modules. Enabling NPF and blacklistd services would normally result in them being automatically loaded as root, but predictably on securelevel=1 this is not going to happen. We have 2 ways out to deal with this:

1) enable them in kernel config

If you're used to running a custom kernel, you can uncomment:

pseudo-device bpfilter # Berkeley packet filter pseudo-device npf # NPF packet filter options NPF_EXT_LOG options NPF_EXT_NORMALISE options BPFJIT options SLJIT

2) make init(8) load them at boot by resorting to modules.conf(5):

$ cat >> /etc/modules.conf << EOF > #additional kernel modules > bpfjit > if_npflog > npf > npf_alg_icmp > npf_ext_log > npf_ext_normalize > sljit > EOF $ echo modules=YES >> /etc/rc.conf

After rebooting, let's check everything's in place:

$ modstat | egrep "npf|jit" NAME CLASS SOURCE^[1] FLAG REFS SIZE REQUIRES bpfjit misc filesys - 0 8491 sljit if_npflog driver filesys - 0 516 - npf driver filesys - 3 40811 bpf npf_alg_icmp misc filesys - 0 1312 npf npf_ext_log misc filesys - 0 669 npf npf_ext_normalize misc filesys a 0 751 npf sljit misc filesys a 1 28794 -

[1] Notice the "filesys" value (in place of "builtin") for the $SOURCE variable, indicating those modules are not built in the currently loaded kernel

In both cases we may want to edit /etc/sysctl.conf by adding:

# Toggle JIT compilation of new filter programs on net.bpf.jit=1 # 4x BPF buffer max size net.bpf.maxbufsize=4194304

....and run:

$ echo create > /etc/ifconfig.npflog0

thus to force ifconfig() to create npflog0 at boot.

Configure blacklistd

Let's write a suitable /etc/blacklistd.conf[2], please refer to blacklistd.conf(5)

# Ban addresses for $disable time on $service after $nfail unsuccessful connection attempts # service type proto owner name nfail disable [local] domain dgram udp named -desk 3 24h ftp stream tcp root -desk 3 24h http stream tcp root -desk 3 24h https stream tcp root -desk 3 24h smtp stream tcp postfix -desk 3 24h submission stream tcp postfix -desk 3 24h ssh stream tcp root -desk 3 24h # use [remote] table to introduce special policies for $addr/mask:port # addr/mask:port type proto owner name nfail disable^[3] [remote] 192.168.1.0/24:ftp stream tcp root -desk 9 = 192.168.1.0/24:ssh stream tcp root -desk 9 =



[2] Disclaimer: at the current state, postfix (and proftpd) support is available only in HEAD

[3] *The "=" value for the $disable variable indicates that the [remote] policy will follow the same behavior as the corresponding [local] one (in this case 24 hours). A wildcard value ('*') would have meant permanent ban *

Make NPF use blacklistd

It's wise to back up your former npf.conf revision beforehand:

$ cp -pi /etc/npf.conf /etc/npf.conf.`date +%s`

Now, let's update our NPF config; in particular, referring to my stateful firewall example, here's the relevant /etc/npf.conf diff:

--- npf.conf 2019-06-08 00:38:31.517536136 +0200 +++ /etc/npf.conf 2019-06-08 01:20:33.955449984 +0200 @@ -13,6 +13,10 @@ $LAN = { 192.168.1.0/24 } $localhost= 127.0.0.1 + # Enable Just-In-Time compilation of filter pro- + # grams sent to the bpf(4) node + set bpf.jit on; + # Load ICMP application-level gateway[4] alg "icmp" @@ -30,6 +34,10 @@ group default { #Pass everything on loop interface pass final on lo0 all + + #Block and release ports on demand to avoid DoS abuse, + #according to blacklistd(8) policies + ruleset "blacklistd-desk" #Block blacklisted IPs block in final from <blacklist>

Restrict r/w permissions to root only

$ chmod 0400 /etc/npf.conf /etc/npf_blacklist /etc/blacklistd.conf

And subsequently update the NPF ruleset:

$ npfctl flush $ npfctl reload

Enable blacklistd

$ echo blacklistd=YES >> /etc/rc.conf

$ service blacklistd start

Perform some tests

According to the my [local] table, WAN clients shall be banned for 24h after 3 failed authentication attempts, while the [remote] table (which takes precedence) introduces milder policies for LAN clients, implying they can try to authenticate up to 9 times before being rejected.

$ blacklistctl dump -ab

address/ma:port id nfail last access 152.19.207.76/32:22 9 11/3 2019/06/08 12:51:25 192.168.1.31/32:21 d 12/9 2019/06/08 12:54:19

The first address is me trying to SSH into my laptop from my phone while on Mobile Data connection (correctly banned after 3 failed auth attempts); the second address is me trying to FTP into my laptop from phone while connected to Wifi (correctly banned after 9 failed auth attempts)

I'm factually negated access

That's all folks, hope you enjoyed it! 🙂