Skip to main content

PF (Packet Filter)

Knocknoc integrates with the PF packet filter to dynamically control network access on a host that the agent runs on directly. As users authenticate and grants expire, the agent adds and removes their IPs from a PF table; your own pass/block rules reference that table by name.

This is a local backend: the agent runs on the same machine as PF and drives it with pfctl. It works anywhere pfctl is available and the agent is supported.

This is not the same as the pfSense backend. pfSense is a remote integration; PF here is the in-kernel packet filter managed locally with pfctl. If you run pfSense or OPNsense, use the vendor-specific backend instead.

What the Agent Does

You point the agent at a PF table. When a user is granted access, the agent adds their IP to that table; when the grant expires or the user logs out, it removes the IP. The agent keeps the table's contents matched to the set of currently granted users.

The table itself enforces nothing. You write pass and/or block rules in pf.conf that reference the table by name, and PF applies them to traffic.

Requirements

  • The Knocknoc agent installed and running on the PF host.
  • PF enabled (pfctl -e).
  • A persistent PF table declared in pf.conf (for example table <knoc_ssh_allowed> persist).
  • One or more pass/block rules referencing the table.

Setup

Step 1: Install the Agent on the PF Host

Deploy the Knocknoc orchestration agent on the machine running PF. The agent connects out to the Knocknoc server over WebSocket; it does not need an inbound port.

See the Linux or OpenBSD setup guide.

Step 2: Declare a PF Table and Rules

Add a persistent table to /etc/pf.conf and write rules that reference it. The table must be persist so it survives with no members. For example, to gate SSH:

table <knoc_ssh_allowed> persist

pass  in quick proto tcp from <knoc_ssh_allowed> to port 22
block drop in quick proto tcp to port 22

The pass rule must come before the block rule, since both are quick. Adjust the protocol, port, and destination to fit your policy. Note the table name; you will enter it in the Knocknoc wizard.

Note, on a standard OpenBSD installation, a set of tables will be created by default.

Step 3: Reload PF

Check the configuration, then load it:

pfctl -nf /etc/pf.conf   # validate without applying
pfctl -f  /etc/pf.conf   # apply
pfctl -e                 # enable PF if it is not already

Step 4: Create the Knoc in Knocknoc

  1. Create a Local firewall Knoc and select the agent running on the PF host.

  2. For the backend type, choose PF (BSD/macOS/Solaris).

    If PF is greyed out, the agent on that host did not report PF as available. Confirm pfctl is on the host and the agent can run it (see troubleshooting).

Step 5: Configure the ACL

Set the PF table the agent writes to (e.g., knoc_ssh_allowed). The wizard lists tables it discovered on the agent; you can also type a new name, but you must then create that table on the host yourself. The table name must match a persist table in pf.conf.

image.png

Step 6: Assign Users and Test

  1. Assign users and/or groups to the Knoc.

  2. Log in as a test user.

  3. On the PF host, confirm the IP appears in the table:

    pfctl -t knoc_ssh_allowed -T show
  4. Verify access through the service the rule protects.

  5. On logout or grant expiry, confirm the IP is removed from the table.

Troubleshooting

Agent Logs

Start with the agent logs on the PF host. To view the agent logs on Debian/RHEL, run `journalctl -xefu knocknoc-agent`. On OpenBSD, run `tail -f -1000 /var/log/daemon | grep knocknoc_agent`.

Common problems:

  • PF backend not offered in the wizard: the agent did not report PF as available. Confirm pfctl is installed on the host, and that the agent runs as root or has working doas/sudo access to pfctl.
  • pf not enabled: PF is installed but off. Enable it with pfctl -e.
  • permission denied: the agent cannot run pfctl with enough privilege. Run it as root, or check the doas/sudo rule scoped to pfctl.
  • Table does not exist / No such table: the table named in the ACL is not declared in pf.conf, or PF has not been reloaded since it was added. Add table <name> persist and run pfctl -f /etc/pf.conf. Table names are case-sensitive and must match the ACL exactly.

IP Added but Access Still Blocked

If the user's IP shows up in the table (pfctl -t <table> -T show) but traffic is still denied:

  1. Confirm pf.conf has a pass rule referencing the table, and that it precedes any block rule (both should be quick).
  2. Reload after editing rules: pfctl -f /etc/pf.conf.
  3. Check the rule's interface, protocol, port, and direction match the traffic you expect.

Entries Disappear or Do Not Persist

  • Ensure the table is declared persist. Without it, PF drops an empty table and the agent cannot add IPs to it.
  • Avoid manually editing or flushing the table; the agent manages its contents and will restore granted IPs, but manual changes can briefly drop access.

PF Documentation References