Skip to main content

Firewalld

IPsets with firewalld

On RHEL-family hosts — and any distribution that uses firewalld as its firewall front-end — Knocknoc does not talk to firewalld directly. As with the main IPSet backend, Knocknoc orchestrates the kernel's native IPSets, and firewalld simply references those sets in a static rule. The set is the source of truth; the firewall rule never changes.

Prerequisites

  • The Knocknoc agent is installed and enrolled — see Linux Agent Installation.
  • The ipset package is present (dnf install ipset; use yum on RHEL 7).
  • You have reviewed the main IPSet page, which this page extends for the firewalld case.

The rule that decides the whole design: the Knocknoc agent owns the IPSet, and firewalld only references it. Never create the set with firewall-cmd --new-ipset.

A firewall-cmd --reload flushes and rebuilds every IPSet that firewalld manages, repopulating it from permanent configuration. That would wipe every live, time-limited grant the agent has added — silently revoking everyone the next time the firewall is reloaded by an administrator or a configuration-management run. Keeping the set outside firewalld's ownership makes the reload harmless.

How Knocknoc creates the IPSets

This is the same mechanism documented on the main IPSet page; nothing about it changes for firewalld. The agent creates the sets on boot from a list of names, one per line:

# /opt/knocknoc-agent/etc/ipset.list
# One ACL / set name per line.
knoc_ssh
knoc_https

For each name, the agent's startup service creates two sets:

Set Type Purpose
knoc_ssh hash:ip IPv4 grants
knoc_ssh_v6 hash:ip family inet6 IPv6 grants

firewalld plays no part in creating them — these are real kernel IPSets that any firewall rule can match against. When a user logs in to Knocknoc and is authorised for the relevant Knoc, the agent adds their source IP to the set; on revoke or expiry, it removes the address.

Adding a set for firewalld to reference

Adding a set is a host-side change, not a firewalld change:

  1. Add the name to /opt/knocknoc-agent/etc/ipset.list (one per line).
  2. Restart the creator service so the sets exist immediately:
    systemctl restart create-ipsets
    
    Enable it on boot if it is not already: systemctl enable create-ipsets.
  3. Verify that the IPv4 and IPv6 sets now exist:
    ipset -L knoc_ssh
    ipset -L knoc_ssh_v6
    
    They will be empty until someone is granted access — this is expected.
  4. Configure the matching Knoc in the Knocknoc server using the same set name.

That is the entire "create" side. Everything below is the static firewalld reference.

firewalld reference examples

Knocknoc-managed sets are referenced from direct rules, not rich rules (see Why direct rules, not rich rules). Direct rules are stored in firewalld's permanent configuration and survive reloads, and they can match a set that firewalld does not own.

Priority 0 is evaluated before 1. The pattern is always allow the set, then deny the rest on the same port.

If the active zone's target already drops unsolicited traffic and the port is not opened as a service or port in that zone, the explicit catch-all DROP rules below are optional. They are shown for clarity and for hosts whose default policy is not already deny.

1. SSH (IPv4)

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \
  -p tcp --dport 22 -m set --match-set knoc_ssh src -j ACCEPT

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 \
  -p tcp --dport 22 -j DROP

firewall-cmd --reload

2. SSH, dual-stack (IPv4 + IPv6)

Add the IPv6 rules against the agent's auto-created _v6 set:

firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 0 \
  -p tcp --dport 22 -m set --match-set knoc_ssh_v6 src -j ACCEPT

firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 1 \
  -p tcp --dport 22 -j DROP

firewall-cmd --reload

3. HTTPS web application

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \
  -p tcp --dport 443 -m set --match-set knoc_https src -j ACCEPT

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 \
  -p tcp --dport 443 -j DROP

firewall-cmd --reload

4. Custom internal application on multiple ports

Add a custom set — for example app_admin — to ipset.list, restart create-ipsets, then gate several ports at once with multiport:

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \
  -p tcp -m multiport --dports 8443,9443 -m set --match-set app_admin src -j ACCEPT

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 \
  -p tcp -m multiport --dports 8443,9443 -j DROP

firewall-cmd --reload

5. Dropping established sessions on revoke

By default the firewall stops new connections when an IP leaves the set; existing TCP sessions can linger until they time out. To terminate them on revoke, use Knocknoc's blocking-set pattern (the ipset_block.sh example on the main IPSet page, which manages the knocknoc_blocked / knocknoc_blocked_v6 sets). Add knocknoc_blocked to ipset.list so the set exists on boot, point the Knoc backend at the block script, then place the blocked-set DROP ahead of the allow rule:

# 0: anything in the blocked set is dropped first — terminates established flows on revoke
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 \
  -m set --match-set knocknoc_blocked src -j DROP

# 1: granted users allowed
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 \
  -p tcp --dport 22 -m set --match-set knoc_ssh src -j ACCEPT

# 2: everyone else denied
firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 2 \
  -p tcp --dport 22 -j DROP

firewall-cmd --reload

As written, the blocked-set rule drops all traffic from a revoked source. To limit it to a single service, scope it with -p tcp --dport 22 like the other rules.

Why direct rules, not rich rules

A rich rule may look like the obvious choice:

# Not suitable for Knocknoc-managed sets
firewall-cmd --permanent --add-rich-rule='rule source ipset="knoc_ssh" ... accept'

The source ipset= syntax only works if firewalld owns the set — that is, you created it with firewall-cmd --new-ipset. That reintroduces the reload-flush problem and hands set management back to firewalld, which conflicts with the agent. Direct rules reference a set purely by name and do not require ownership, which is what this design needs.

Common pitfalls

  • Do not also open the port in the zone. If SSH is permitted as a normal service or port in the active zone (--add-service=ssh, --add-port=22/tcp), it is open to everyone and the gate is bypassed. The direct ACCEPT rule must be the only path in.
  • Do not run --new-ipset for Knocknoc sets. The agent owns them.
  • RHEL 8/9/10 use the nftables backend. Direct rules route through iptables-nft, and -m set --match-set matches the IPSet via the xt_set module — this works. Make sure the ipset package is installed.
  • Rule order matters. Allow before the catch-all deny; the blocked-set DROP before the allow.

Verify and test

# Sets exist (empty until a grant)
ipset -L knoc_ssh

# Direct rules are in place
firewall-cmd --direct --get-all-rules

Log in as an authorised user in Knocknoc, then connect to the service. The agent adds the source IP to the set on login — confirm with ipset -L knoc_ssh, where the address should appear as a member. Log out or let the grant expire and it is removed; the firewall rule never changes.

See also