firewalld

The proposed way to ban IPs using firewalld uses one reaction zone.

The IPs are banned on all ports, meaning banned IPs won't be able to connect on any service.

Start/Stop

We first need to create this zone on startup.

{
  start: [
    // create the new zone
    ['firewall-cmd', '--permanent', '--new-zone', 'reaction'],
    // set its target to DROP
    ['firewall-cmd', '--permanent', '--set-target', 'DROP', '--zone', 'reaction'],
    // reload firewalld to be able to use the new zone
    ['firewall-cmd', '--reload'],
  ],
}

We want reaction to remove it when quitting:

{
  stop: [
    // remove the zone
    ['firewall-cmd', '--permanent', '--delete-zone', 'reaction'],
    // reload firewalld
    ['firewall-cmd', '--reload'],
  ],
}

Ban/Unban

Now we can ban an IP with this command:

{
  cmd: ['firewall-cmd', '--zone', 'reaction', '--add-source', '<ip>'],
}

And unban the IP with this command:

{
  cmd: ['firewall-cmd', '--zone', 'reaction', '--remove-source', '<ip>']
}

A good practice is to wrap the actions in a function with parameters:

local banFor(time) = {
  ban: {
    cmd: ['firewall-cmd', '--zone', 'reaction', '--add-source', '<ip>'],
  },
  unban: {
    cmd: ['firewall-cmd', '--zone', 'reaction', '--remove-source', '<ip>']
    after: time,
  },
};

See how to merge different actions in JSONnet FAQ

Real-world example

local banFor(time) = {
  ban: {
    cmd: ['firewall-cmd', '--zone', 'reaction', '--add-source', '<ip>'],
  },
  unban: {
    after: time,
    cmd: ['firewall-cmd', '--zone', 'reaction', '--remove-source', '<ip>']
  },
};

{
  patterns: {
    // IPs can be IPv4 or IPv6
    // ip46tables (C program also in this repo) handles running the good commands
    ip: {
      regex: '...', // See patterns.md
    },
  },

  start: [
    ['firewall-cmd', '--permanent', '--new-zone', 'reaction'],
    ['firewall-cmd', '--permanent', '--set-target', 'DROP', '--zone', 'reaction'],
    ['firewall-cmd', '--reload'],
  ],
  stop: [
    ['firewall-cmd', '--permanent', '--delete-zone', 'reaction'],
    ['firewall-cmd', '--reload'],
  ],

  streams: {
    // Ban hosts failing to connect via ssh
    ssh: {
      cmd: ['journalctl', '-fn0', '-u', 'sshd.service'],
      filters: {
        failedlogin: {
          regex: [
            @'authentication failure;.*rhost=<ip>',
            @'Connection reset by authenticating user .* <ip>',
            @'Failed password for .* from <ip>',
          ],
          retry: 3,
          retryperiod: '6h',
          actions: banFor('48h'),
        },
      },
    },
  },
}