Configuration

The config file is TOML, by default /usr/local/etc/ereshkigal.toml (overridable with ereshkigal start --config <path>). Top level keys are manager settings; each hash under kur defines one underworld, named for the hash — the hash at kur.sshd is the kur instance sshd. Names must match /^[a-zA-Z0-9-]+$/.

Manager settings

| key | default | what | |--------------------|-------------------------|----------------------------------------------------------------| | socket_group | root's default group | group ownership of the manager socket | | socket_mode | "0660" | perms on the manager socket, as a string, processed via oct | | run_base_dir | /var/run/ereshkigal | sockets and PID files live under here | | cache_base_dir | /var/cache/ereshkigal | the clay tablets (ban state CSVs) live here | | kur_bin | kur | the kur bin the manager spawns | | timeout | 30 | seconds the manager waits on a kur socket | | ban_time | 600 | seconds a ban lasts; 0 = eternal residence | | checkpoint | 60 | seconds between tablet recopies; 0 = mutations/stop only | | enable_cidr | false | whether whole ranges may be banished — see below | | cidr_silent_drop | false | drop rather than error CIDR commands where CIDR is unavailable | | enable_auth | false | Neti at the gate — see security | | authed_users | [] | users with global access (with enable_auth) | | authed_groups | [] | groups with global access (with enable_auth) | | auth_temp_dir | system tmpdir | where the auth challenge cookie files go |

Kur sockets are always mode 0600 — that is not configurable, and security explains why it must stay that way.

Kur settings

Inside a [kur.<name>] hash...

| key | what | |--------------------|-----------------------------------------------------------------------------------| | backend | required unless fan_out is set; see kurs for more info | | fan_out | array of other kur names, in place of backend; makes this a gate (see below) | | ports | array of ports to block for; all if unset | | protocols | array of protocols to block for; backend-dependent default if unset | | prefix | rule/table/chain name prefix, default kur | | self_heal | verify and re-init the firewall setup before each ban/unban, default 1 | | ban_time | this underworld's default sentence, overriding the top level one | | checkpoint | this underworld's tablet recopy interval, overriding the top level one | | enable_cidr | whether this underworld banishes ranges, overriding the top level one | | cidr_silent_drop | this underworld's drop-vs-error handling for CIDR, overriding the top level one | | options | a hash of backend specific options; see each backend's page under kurs | | authed_users | users granted access to this kur, expanding the global list | | authed_groups | groups granted access to this kur, expanding the global list |

Gates — fan_out kurs

A kur hash may carry fan_out, an array of other kur names, in place of backend:

[kur.baphomet]
fan_out      = [ "sshd", "smtp" ]
authed_users = [ "baphomet" ]

Such a kur is a gate — one name that opens onto several underworlds. It has no process and no socket of its own; commands targeted at it (ban --kur, cidr-ban --kur, checkpoint <name>, re-init <name>, clear-retries <name>, status <name>) fan out to its members instead, with results reported per member.

With enable_auth on, a command aimed at a gate is authorized against the gate's own lists, not its members'. That is what a gate buys: an outside integration — a log watcher, IDS glue — can be granted the gate alone and reach a whole set of underworlds through it, without being listed on any member, or knowing they exist.

Members must be real kurs (gates may not nest), and untargeted commands (ban/cidr-ban with no --kur, unban, cidr-unban, banned, and bare checkpoint, re-init, and clear-retries) never touch gates, only real kurs. In status, a gate shows its member list and counts as running when every member is.

How ban_time layers

The most specific setting wins:

per request --ban-time  >  kur ban_time  >  top level ban_time  >  600

0 at any layer means the ban never expires. checkpoint layers the same way, minus the per-request level.

Banishing ranges

enable_cidr opens the cidr-ban/cidr-unban commands (see usage). It is off by default and layers per kur — a top level enable_cidr = true turns it on everywhere, a [kur.<name>] enable_cidr overrides it for that one underworld either way.

enable_cidr = true          # ranges may be banished everywhere...

[kur.sshd]
backend = "pf"

[kur.abuseipdb]
backend     = "abuseipdb"
enable_cidr = false         # ...but not through this one

Enabling it is only half the story — the backend has to be able to carry ranges. Table and set based backends (pf, ipfw, ufw, npf, linux_ip_route, shorewall, openwrt, and most of the appliance and cloud backends) can; iptables, nftables, firewalld, xdp, hosts_deny, dns_rpz, nsupdate, abuseipdb, and netscaler can not. A kur that has enable_cidr set on a backend that cannot carry ranges logs a warning at startup and refuses range commands.

cidr_silent_drop decides how such a kur — CIDR off, or a backend that cannot oblige — answers a range command. Off (the default), it returns an error. On, it quietly drops the command, reporting dropped:1. The point is fan-outs: with a gate spanning range-capable and range-incapable underworlds, setting cidr_silent_drop on the incapable ones lets a single cidr-ban land where it can without the rest souring the response.

Single IP ban/unban are unaffected by either toggle, and unban --all empties ranges alongside single IPs regardless.

A complete example

# the world above
socket_group = "wheel"      # who may speak to the manager...
# 0660 would normally be better, but in this example
# involves multiple groups having access
socket_mode  = "0666"       # ...via group membership on the socket
ban_time     = 600          # ten minute sentences unless told otherwise
checkpoint   = 60           # recopy the tablets every minute

# Neti at the gate... identity checks on top of the socket perms
enable_auth   = true
# Neti allows the user admin to access everything
authed_users  = [ "admin" ]
# Neti allows all users of the group wheel to access everything
authed_groups = [ "wheel" ]

# the sshd underworld... hour long sentences, and sever the states of
# anyone sent below
[kur.sshd]
backend   = "pf"
ports     = [ "22" ]
protocols = [ "tcp" ]
ban_time  = 3600

[kur.sshd.options]
kill = 1

# the smtp underworld
[kur.smtp]
backend   = "pf"
ports     = [ "25", "465", "587" ]
protocols = [ "tcp" ]

# the web underworld
[kur.web]
backend   = "pf"
ports     = [ "80", "443" ]
protocols = [ "tcp" ]
# Neti allows all members of the www access to this underworld...
# meaning if your webapps can manage their own bans
authed_groups = [ "www" ]

[kur.web.options]
kill = 0


# eternal residence for the manually curated
[kur.blocklist]
backend  = "pf"
ban_time = 0

[kur.blocklist.options]
kill = 1

Config changes take effect on restart. Kurs added at runtime with ereshkigal add are not written back to this file — to make one permanent, add its hash here.