NAME

Net::Firewall::BlockerHelper::backends::file_reload - Render banned IPs to a file and run a reload hook.

VERSION

Version 0.2.0

SYNOPSIS

The rendered file has no effect on its own; whatever is meant to consume it must be configured to read it and the reload option set to whatever makes it re-read it. So for the nginx example below, nginx needs a line like the following in the relevant http or server context.

include /etc/nginx/blocklist.conf;

Also worth noting is that with remove_on_teardown left at its default of 1, teardown unlinks the file, so a consumer that errors on a missing include, nginx included, will then fail to reload or start till the file exists again. For consumers like that either set remove_on_teardown to 0, which leaves an empty file in place instead, or make sure the file gets created before the consumer needs it.

use Net::Firewall::BlockerHelper;

my $fw_helper = Net::Firewall::BlockerHelper->new(
    backend => 'file_reload',
    name    => 'blocklist',
    options => {
        file   => '/etc/nginx/blocklist.conf',
        format => 'deny %%%BAN%%%;',
        reload => 'systemctl reload nginx',
    },
);

$fw_helper->init_backend;
$fw_helper->ban( ban => '1.2.3.4' );
$fw_helper->unban( ban => '1.2.3.4' );
$fw_helper->teardown;

DESCRIPTION

This is a generic backend for anything that reads a list of IPs from a file and needs to be told to reload after that file changes: web servers, DNS RPZ zones, hand rolled ipset restore files, and so on.

The full set of currently banned IPs is rendered to file from in-memory state on every change; the file is never parsed back, so this module is the sole author of its contents. After each write the optional reload command is run. Because the file is rewritten wholesale, ban/unban are idempotent and there is never any partially applied state.

METHODS

new

- options :: A hash of options. See below.
    - Default :: {}

- name :: Required by Net::Firewall::BlockerHelper, otherwise unused.
    - Default :: undef

The options hash accepts the following.

- file :: Path of the file to render the banned IPs to. Required.
    - Default :: undef

- reload :: Command run after the file is written. '2>&1' is appended.
        If undef or blank, nothing is run and only the file is updated.
    - Default :: undef

- blank_reload_error :: If true, the reload command producing no output
        at all is treated as a failure, even with a zero exit. Set to 0
        for reload commands that are silent on success, such as
        'systemctl reload nginx'.
    - Default :: 1

- format :: Per-IP line template. '%%%BAN%%%' is replaced with the IP.
        The rendered lines are newline joined.
    - Default :: '%%%BAN%%%'

- header :: String emitted at the top of the file, before the IP lines.
    - Default :: ''

- footer :: String emitted at the bottom of the file, after the IP lines.
    - Default :: ''

- check :: Optional command used by check(). A zero exit is healthy. If
        not set, check() verifies the file still exists instead.
    - Default :: undef

- remove_on_teardown :: If true, teardown unlinks the file. If false, the
        file is rendered empty (header/footer only) and left in place.
    - Default :: 1

All errors are considered fatal, meaning if new fails it will die.

init

Initiates the backend, rendering an initial (empty unless bans were pre-seeded) file and running the reload hook.

Note that the consumer must be configured to read the rendered file for any of this to have an effect. See "SYNOPSIS".

ban

Bans an IP. The value of ban is validated as being a IPv4 or IPv6 address and lowercased, then added to the ban list. The file is re-rendered and the reload command, if one is configured, is run. Banning an already banned IP is a noop.

$fw_helper->ban( ban => $ip );

unban

Unbans an IP. The value of ban is validated as being a IPv4 or IPv6 address and lowercased, then removed from the ban list. The file is re-rendered without it and the reload command, if one is configured, is run. Unbanning an IP that is not banned is a noop.

$fw_helper->unban( ban => $ip );

ban_cidr

Bans a CIDR range by rendering it into the file the same way a single IP is.

$fw_helper->ban_cidr(ban => '1.2.3.0/24');

unban_cidr

Unbans a CIDR range by re-rendering the file without it.

$fw_helper->unban_cidr(ban => '1.2.3.0/24');

list_cidr

List banned CIDR ranges.

my @banned_cidrs = $fw_helper->list_cidr;

list

List banned IPs. Returns an array of the currently banned single IPs. CIDR ranges are not included; for those see "list_cidr".

my @banned = $fw_helper->list;

re_init

Re-renders the file from the retained ban list and runs the reload hook. As the file is always rendered from full state, nothing needs tearing down first; a single re-render restores everything.

teardown

Removes the rendered file (or empties it when remove_on_teardown is false) and runs the reload hook.

stop

Alias for "teardown".

check

If a check command is configured, runs it and treats a zero exit as healthy. Otherwise verifies the rendered file still exists.

flush

Removes all currently banned IPs at once by clearing the ban list and re-rendering the now empty file.

ERROR CODES / FLAGS

Error handling is provided by Error::Helper. All errors are considered fatal.

1, notInited

The backend has not been inited yet.

8, optionsNotHash

The item passed to new for options is not a hash.

9, noBanItem

No IP or CIDR range specified to ban or unban.

10, banItemNotIP

The item to ban is not an IP. Either wrong ref type or regexp test using Regexp::IPv4 and Regexp::IPv6 failed.

12, backendInitError

Failed to init the backend.

13, banFailed

Failed to ban the item.

14, unbanFailed

Failed to unban the item.

15, listFailed

Failed to get a list of bans.

16, reInitFailed

Failed to re_init the backend.

17, teardownFailed

Failed to teardown the backend.

18, alreadyInited

init called, but the backend has already been inited.

24, checkFailed

The backend check raised an error.

25, flushFailed

Failed to flush the bans.

30, fileNotDefined

The option file is undef or blank.

31, fileWriteFailed

Could not open the file for writing.

32, banCidrFailed

Failed to ban the CIDR range.

33, unbanCidrFailed

Failed to unban the CIDR range.

34, cidrItemNotCidr

The item to ban is not a CIDR range. Either wrong ref type or it is not an IPv4 or IPv6 address followed by a prefix length valid for its family.

35, cidrNotSupported

The backend does not support CIDR bans.

36, listCidrFailed

Failed to get a list of CIDR bans.

AUTHOR

Zane C. Bowers-Hadley, <vvelox at vvelox.ent>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2023 by Zane C. Bowers-Hadley.

This is free software, licensed under:

The GNU Lesser General Public License, Version 2.1, February 1999