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