NAME
Ereshkigal - Handle firewall or similar bans.
VERSION
Version 0.0.1
SYNOPSIS
use Ereshkigal;
my $ereshkigal = Ereshkigal->new( config => '/usr/local/etc/ereshkigal.toml' );
$ereshkigal->start_server;
Ereshkigal is a ban manager for firewalls. It wrangles various Ereshkigal::Kur instances, spawned via the kur bin, each of which runs as its own process and uses Net::Firewall::BlockerHelper for talking to the firewall.
The manager listens on a unix socket, by default /var/run/ereshkigal/socket, speaking the newline delimited JSON protocol of POE::Component::Server::JSONUnix, and proxies per instance work to the kur sockets under /var/run/ereshkigal/kur/.
CONFIG FILE
The config file is TOML. Hashes under kur define instances. The instance name is the hash name, so the hash at kur.sshd is the kur instance sshd. Keys inside are what kur/Net::Firewall::BlockerHelper take... backend, ports, protocols, prefix, self_heal, and the backend specific options table.
Values in the options table must be plain scalars, with one exception... interfaces, which backends such as xdp want as an array, may be given as one. Any other array or table valued option is refused at config load rather than being handed to the backend as a stringified ref.
Top level keys are manager settings.
- socket_group :: Group ownership of the manager socket.
Default :: the default group of the root user
- socket_mode :: Perms for the manager socket. Processed via oct, so
should be specified as a string such as "0660". Kur sockets are
always 0600 and not configurable.
Default :: 0660
- run_base_dir :: Base dir for run files.
Default :: /var/run/ereshkigal
- cache_base_dir :: Base dir for cache files, passed to kur.
Default :: /var/cache/ereshkigal
- kur_bin :: The kur bin to spawn instances with.
Default :: kur
- timeout :: Timeout in seconds used when talking to kur sockets. For
commands touching multiple kurs this bounds the whole fan out
rather than each kur individually.
Default :: 30
- ban_time :: How long bans should last in seconds. 0 means bans never
time out. May be overridden per kur via ban_time in its hash and
per ban request.
Default :: 600
- checkpoint :: Seconds between periodic rewrites of each kur's ban
state CSV. 0 disables the periodic rewrite... ban/unban, stop,
and on demand checkpoints still happen. May be overridden per kur
via checkpoint in its hash.
Default :: 60
- enable_cidr :: Whether CIDR banning is enabled. May be overridden per
kur via enable_cidr in its hash. Even when set, the cidr_ban and
cidr_unban commands only work on a kur whose backend supports CIDR
bans.
Default :: false
- cidr_silent_drop :: How a kur handles a CIDR command when CIDR banning
is not available for it, either because enable_cidr is off or the
backend cannot do CIDR. When set such a kur silently drops the
command rather than erroring, which keeps a fan out across a mix of
CIDR capable and incapable kurs from being spoiled by the incapable
ones. May be overridden per kur via cidr_silent_drop in its hash.
Default :: false
- enable_auth :: Enables the L<POE::Component::Server::JSONUnix>
auth_required cookie file ownership challenge on the manager
socket, along with authorization via authed_users/authed_groups.
Default :: 0
- authed_users :: An array of users with global access.
Default :: []
- authed_groups :: An array of groups with global access.
Default :: []
- auth_temp_dir :: Dir used for the ownership challenge cookie files,
passed through to L<POE::Component::Server::JSONUnix>.
Default :: undef
A kur hash may instead carry fan_out, an array of other kur names, in place of backend. Such a kur is manager side only... no process and no socket of its own. Commands targeted at it (ban and cidr_ban with args.kur, checkpoint, re_init, and clear_retries with args.kur, and status_kur) fan out to its members instead, making it usable as a single point of contact for driving a whole set of kurs. With enable_auth on, authorization for a command targeted at a fan_out kur is checked against the fan_out kur's own lists rather than its members', so an integration may be granted just the gate without being listed on any member. Members must be defined non fan_out kurs... fan_out kurs may not nest. Untargeted commands (ban and cidr_ban without args.kur, unban, cidr_unban, banned, and checkpoint, re_init, and clear_retries without args.kur) touch only real kurs, never fan_out ones.
[kur.baphomet]
fan_out = [ "sshd", "smtp" ]
authed_users = [ "baphomet" ]
Each kur hash may also carry its own authed_users/authed_groups, which expand upon the global ones for that kur... the effective lists for a kur are the global ones plus its own. A command must be authorized for every kur it touches, with untargeted fan-out commands touching every kur, while commands about the manager itself (stop, add_kur, remove_kur, and the whole manager views status/status_all) require the global lists. UID 0 is always authorized. The kur backends do no checking at all... their sockets are 0600 and only ereshkigal is expected to be able to write to them, so enforcement is entirely the manager's responsibility.
A refused command comes back as a normal JSONUnix error response carrying a machine-readable code, permission_denied for an authorization refusal, matching the code field convention of POE::Component::Server::JSONUnix's own permission and auth errors, so a consumer may branch on the code rather than matching the message text.
Example...
socket_group = "wheel"
socket_mode = "0660"
[kur.sshd]
backend = "ipfw"
ports = [ "22" ]
protocols = [ "tcp" ]
METHODS
new
Initiates the object. All errors are considered fatal, meaning if new fails it will die.
- config :: Path to the TOML config file.
Default :: /usr/local/etc/ereshkigal.toml
socket_path
Returns the path of the manager unix socket.
my $socket_path = $ereshkigal->socket_path;
pid_path
Returns the path of the manager PID file.
my $pid_path = $ereshkigal->pid_path;
kur_socket_path
Returns the path of the unix socket for the specified kur instance.
my $kur_socket_path = $ereshkigal->kur_socket_path($name);
start_server
Starts the manager. Spawns all configured kur instances, each supervised and restarted with a backoff should it die, and brings up the POE::Component::Server::JSONUnix server on the manager socket, then calls $poe_kernel->run.
This should not be expected to return till the manager is told to stop.
After binding, the manager socket is chowned to the configured group and chmoded to the configured mode.
The JSON commands handled are as below.
- status :: Manager status... uptime and kur list with up/down state.
- status_all :: The above plus each kur's full status block.
- status_kur :: Full status of the kur instance args.name. For a
fan_out kur this is its member list plus each member's status.
- banned :: Banned IPs, grouped per kur, along with when each expires.
- ban :: Ban the IPs args.ips on the kur args.kur, or on all kurs if
args.kur is not specified. If args.kur is a fan_out kur it expands
to its members. args.ban_time, if defined, is forwarded
to the kurs, overriding their default for how long the bans should
last in seconds, with 0 meaning never time out. IPs are validated
and normalized to their canonical form before being fanned out,
with anything failing to validate reported back per IP under
rejected rather than being sent to the kurs. If nothing validates
the request as a whole errors.
- unban :: If args.all is true, flush every kur. Otherwise validate and
normalize args.ip, erroring if it fails to validate, then check
each kur for it and unban it from each kur it is present on.
- cidr_ban :: Ban the CIDR ranges args.cidrs, otherwise behaving like
ban including the args.kur targeting and args.ban_time forwarding.
CIDRs are validated and reduced to their canonical network form
before being fanned out. A targeted kur, or an untargeted fan out,
for which CIDR is not available answers per kur with either a drop
or an error depending on its cidr_silent_drop.
- cidr_unban :: Validate and normalize args.cidr, erroring if it fails to
validate, then check each kur for it and unban it from each kur it
is present on. There is no all form... unban with args.all already
flushes CIDR bans alongside single IP bans.
- add_kur :: Define and start a new kur instance, args.name and
args.opts. Does not rewrite the config file.
- remove_kur :: Stop the kur instance args.name and deregister it. Does
not rewrite the config file.
- checkpoint :: Force the kur args.kur, or all kurs if args.kur is not
specified, to write their ban state CSVs out now. If args.kur is a
fan_out kur it expands to its members.
- re_init :: Have the kur args.kur, or all kurs if args.kur is not
specified, tear their firewall setup down and rebuild it, re-banning
everything their ban book carries. Expands a fan_out kur the same
way checkpoint does. Bans are briefly not enforced while the setup
is being rebuilt.
- clear_retries :: Have the kur args.kur, or all kurs if args.kur is not
specified, forget unbans still owed to the firewall. args.ip or
args.cidr, at most one of them, names a single owed unban to
forget rather than the lot. Expands a fan_out kur the same way
checkpoint does. Nothing is asked of the firewall, so anything
genuinely still banished there stays that way.
- stop :: Stop all kur instances and then the manager.
ERROR CODES / ERROR FLAGS
Error handling is provided by Error::Helper. All errors are considered fatal.
1, configReadFailed
Failed to read the config file.
2, configParseFailed
Failed to parse the config file as TOML.
3, invalidKurDef
A kur def in the config is invalid... bad name, not a hash, lacking a backend or a fan_out, having both, an invalid fan_out (not an array of kur names, an unknown member, or a nested fan_out kur), or an options table carrying a non-scalar value for anything other than interfaces.
4, runBaseDirError
The run base dir or the kur dir under it could not be created or is not read/writable.
5, badSocketGroup
Failed to resolve the socket group to a GID.
6, invalidBanTime
ban_time is not a non-negative int of seconds.
7, invalidCheckpoint
checkpoint is not a non-negative int of seconds.
8, invalidAuthedList
authed_users or authed_groups is not an array of strings.
AUTHOR
Zane C. Bowers-Hadley, <vvelox at vvelox.net>
BUGS
Please report any bugs or feature requests via GitHub at https://github.com/LilithSec/Ereshkigal/issues, or to bug-ereshkigal at rt.cpan.org.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Ereshkigal
You can also look for information at:
GitHub (source and issues)
RT: CPAN's request tracker
CPAN Ratings
Search CPAN
LICENSE AND COPYRIGHT
This software is Copyright (c) 2023 by Zane C. Bowers-Hadley.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)