nftables

⚠️ Warning: this plugin is beta. If if doesn't work, wait for the next release, that aims to fix those issues.

Permits to manipulate nftables ip sets much faster than with UNIX commands.

It's also more convenient to use. It easily permits to manage one ip set by Filter and thus avoid IP conflicts between filters.

The plugin will add an inet table named reaction to nftables, and remove it when quitting.

systemd options

This plugin requires the CAP_NET_ADMIN and the CAP_PERFMON capabilities to be able to talk to linux's netfilter subsystem.

{
  plugins: {
    nftables: {
      path: '/usr/bin/reaction-plugin-nftables',
      systemd_options: {
        CapabilityBoundingSet: ['~CAP_NET_ADMIN', '~CAP_PERFMON'],
      }
    },
  },
}
plugins:
  nftables:
    path: /usr/bin/reaction-plugin-nftables
    systemd_options:
      CapabilityBoundingSet: ['~CAP_NET_ADMIN', '~CAP_PERFMON']

Configuration

This plugin exposes only one action of type nftables.

{
  ban: {
    type: 'nftables',
    options: {
      set: 'reaction',
      action: 'add',
    },
  },
  unban: {
    type: 'nftables',
    options: {
      set: 'reaction',
      action: 'delete',
    },
    after: '6h',
  },
}
ban:
  type: nftables
  options:
    set: reaction
    action: add
unban:
  type: nftables
  options:
    set: reaction
    action: delete
  after: 6h

Action Options

Each action has 3 possible options:

set

Required. The name of the ip set that will be created.

action

Can be add (add the IP to the set) or delete (delete the IP from the set).

Defaults to add.

pattern

The name of the Pattern that holds the ip in this Filter. It is most commonly ip, but can be set to any name, depending on your configuration.

{
  patterns: {
    ipv4: {
      type: 'ipv4',
    },
  },
  streams: {
    s1: {
      filters: {
        f1: {
          regex: ['^<ipv4> ...'],
          actions: {
            ban: {
              type: 'nftables',
              options: {
                set: 'reaction',
                pattern: 'ipv4',
              }
            }
          }
        }
      }
    }
  }
}
patterns:
  ipv4:
    type: 'ipv4'

streams:
  s1:
    filters:
      f1:
        regex: ['^<ipv4> ...']
        actions:
          ban:
            type: 'nftables'
            options:
              set: 'reaction'
              pattern: 'ipv4'

Set Options

In addition to the previous options, 4 set-related options can be added.

They'll be merged between all actions of the same set.

Conflicting options for a same set in different actions will throw a configuration error.

version

One of ip, ipv4 or ipv6.

IP sets can only be of type ipv4 or ipv6.

  • Specifying ipv4 will create an set capable of containing IPv4 addresses only.
  • Specifying ipv6 will create an set capable of containing IPv6 addresses only.
  • Specifying ip (the default) will create two sets:
    • One IPv4 set, with name suffixed by v4.
    • One IPv6 set, with name suffixed by v6.

The following example will create 4 sets:

  • An IPv4 set named a.
  • An IPv6 set named b.
  • An IPv4 set named cv4.
  • An IPv6 set named cv6.
{
  a1: {
    ipv4only: true,
    type: 'nftables',
    options: {
      set: 'a',
      version: 'ipv4',
    },
  },
  a2: {
    ipv6only: true,
    type: 'nftables',
    options: {
      set: 'b',
      version: 'ipv6',
    },
  },
  a3: {
    type: 'nftables',
    options: {
      set: 'c',
      version: 'ip',
    },
  },
}
a1:
  ipv4only: true
  type: nftables
  options:
    set: a
    version: ipv4
a2:
  ipv6only: true
  type: nftables
  options:
    set: b
    version: ipv6
a3:
  type: nftables
  options:
    set: c
    version: ip

In summary:

  • The version ip is just a convenient shortcut for transparently handling both ipv4 and ipv6 and is the default.
  • If you need more control, or have an ipv4-only or ipv6-only server, you can set version to ipv4 or ipv6.

hooks

The nftables hooks where the set must be inserted.

Array of strings. Defaults to ["input", "forward"], but can be an array with any of those values:

  • ingress
  • prerouting
  • forward
  • input
  • output
  • postrouting
  • egress

See the nftables wiki for details about what those hooks mean.

Example:

{
  type: 'nftables',
  options: {
    set: 'reaction',
    // only in input, not in forward
    hooks: ['input'],
  }
}
type: nftables
options:
  set: reaction
  # only in input, not in forward
  hooks: ['input']

target

The target associated with the match of an IP.

Defaults to drop, which means that associated packets are simply dropped.

But it can be any of those nftables statements:

  • accept
  • drop
  • continue
  • return

Example:

{
  type: 'nftables',
  options: {
    set: 'reaction',
    // user-defined chain
    target: 'nixos-fw-log-refuse',
  }
}
type: 'nftables'
options:
  set: 'reaction'
  # user-defined chain
  target: 'nixos-fw-log-refuse'

timeout

Optional nftables timeout, letting linux/netfilter handle set removal instead of reaction.

If this is used instead of an after action, note that:

  • reaction show and reaction flush won't work.
  • IPs won't be persisted to disk.

Same syntax as Action after.

Example:

{
  type: 'nftables',
  options: {
    set: 'reaction',
    action: 'add',
    timeout: '1d',
  }
}
type: nftables
options:
  set: reaction
  action: add
  timeout: 1d

We recommend using an after command like this instead:

{
  type: 'nftables',
  options: {
    set: 'reaction',
    action: 'add',
  }
},
{
  type: 'nftables',
  options: {
    set: 'reaction',
    action: 'delete',
  },
  after: '1d',
}

Full configuration example

{
  plugins: {
    nftables: {
      path: '/usr/bin/reaction-plugin-nftables',
      systemd_options: {
        CapabilityBoundingSet: ['~CAP_NET_ADMIN', '~CAP_PERFMON'],
      }
    },
  },

  patterns: {
    ip: {
      type: 'ip',
    },
  },

  streams: {
    ssh: {
      cmd: ['journalctl', '-n0', '-fu', 'sshd.service'],
      filters: {
        authfail: {
          regex: [
            'authentication failure;.*rhost=<ip>'
          ],
          retry: 3,
          retryperiod: '3h',
          actions: {
            ban: {
              type: 'nftables',
              options: {
                set: 'ssh',
                action: 'add',
              },
            },
            unban: {
              after: '24h',
              type: 'nftables',
              options: {
                set: 'ssh',
                action: 'delete',
              },
            },
          },
        },
      },
    },

    nginx: {
      cmd: ['tail', '-f', '/var/log/nginx/access.log'],

      filters: {
        directus: {
          regex: [ @'^<ip> .* "POST /auth/login HTTP/..." 401', ],
          retry: 6,
          retryperiod: '4h',
          actions: {
            ban: {
              type: 'nftables',
              options: {
                set: 'directus',
                action: 'add',
              },
            },
            unban: {
              after: '4h',
              type: 'nftables',
              options: {
                set: 'directus',
                action: 'delete',
              },
            },
          },
        },
      },
    },
  },
}

This will produce the following nftables config:

table inet reaction {
	set directusv4 {
		type ipv4_addr
		flags interval
	}

	set directusv6 {
		type ipv6_addr
		flags interval
	}

	set sshv4 {
		type ipv4_addr
		flags interval
	}

	set sshv6 {
		type ipv6_addr
		flags interval
	}

	chain forward {
		type filter hook forward priority filter; policy accept;
		ip saddr @directusv4 drop
		ip6 saddr @directusv6 drop
		ip saddr @sshv4 drop
		ip6 saddr @sshv6 drop
	}

	chain input {
		type filter hook input priority filter; policy accept;
		ip saddr @directusv4 drop
		ip6 saddr @directusv6 drop
		ip saddr @sshv4 drop
		ip6 saddr @sshv6 drop
	}
}