Egress Firewall
Security → Egress. nftables + cgroup v2 vmap rules that restrict each user’s outbound traffic. M34, ADR-0084.
Why per-user egress matters
A compromised tenant whose PHP scripts can call any external host becomes an excellent platform for outbound abuse (command-and-control, credential stuffing, spam-relay). Restricting outbound at the per-user cgroup level bounds the damage even when the host application is owned.
Default policy
Per-user, the nftables ruleset:
- Allows
:443/tcpto anywhere (HTTPS — legitimate API use). - Allows
:587/tcpand:465/tcpto the panel’s own mail host (so PHP scripts can submit mail). - Allows
:993/tcpto the panel’s own mail host (IMAP submission of replies, if needed). - Allows
:53/udpand:53/tcpto127.0.0.1(the loopback recursor). - Drops everything else.
The package’s egress_policy field selects this default or unrestricted (no per-user filtering).
Per-user overrides
Users → Edit → Egress allows the admin to:
- Permit specific destination CIDRs on specific ports.
- Permit FQDN-based destinations (resolved at apply time; refreshed on a daily timer).
- Block specific ports / destinations even when otherwise allowed.
Each rule is namespaced per-user; no rule from one user can affect another.
Implementation
- Each user runs in their own systemd slice (
user-<UID>.slice). - nftables uses a
meta cgroupv2match against the slice id. - A vmap maps
cgroupv2-id → rulesetfor O(1) classification. - The reconciler converges the ruleset on each tick.
The setup was live-verified with the drop counter incrementing on a blocked port and :443 continuing to work.
Per-page surface
- Per-user table — current policy, exception count, drop count (last hour, 24 h).
- Per-user drill-in — the active ruleset rendered as a readable summary plus the raw nftables fragment.
- Test connectivity — pick a user and a destination; the page invokes a one-shot agent call that runs the same nftables decision against the simulated socket and returns “allowed” or “dropped”.
Notes
- nftables rules are stateless at the layer the panel manages; established connections are not interrupted when a rule changes.
- A user’s per-user PHP-FPM pool inherits the slice automatically — no extra wiring per pool.
- The egress firewall does not interpose between the user and inbound traffic; inbound firewalling is UFW plus CrowdSec Decisions.
CLI
jabali per-user egress list
jabali per-user egress allow --user <id> --port 9418 --proto tcp --dest github.com
jabali per-user egress revoke <rule-id>