Tag: nftables

  • Patronus dev log 5: security holes, netlab, and the 0.2.0 release

    Patronus dev log 5: security holes, netlab, and the 0.2.0 release

    Part 5 in the Patronus dev log series.

    Backdated, written in November 2026 from git and CI records. Part 4 ended with the installer test paused.

    October 16: two things at once

    First, the installer test moved off the Mac. It now runs on a KVM runner with QEMU directly: install with the ISO attached, then a cold boot of the disk with the media removed. A live system in about 25 seconds, install plus reboot about 6 minutes, and the installed system comes up on rpool with zpool ONLINE. The first green run is recorded in the commit. Getting there found five real installer bugs, which the commit lists: a bad parted mkpart fs-type for ZFS, a free-space calculation that started partition 2 at the disk end, a root pool that kept its temporary mountpoint forever, pool features GRUB couldn’t read, and chroot bind mounts that stacked and never unmounted, so the pool export failed and the next boot refused to import it. From then to November 6 that workflow has 25 passing runs and 3 cancelled ones.

    Second, the security commits of the same day. From the commit messages:

    patronus-web created 'admin'/'admin' and 'operator'/'operator' accounts on
    every install, so anyone reaching :8443 could take over the firewall.
    SECURITY (CRITICAL): only /api/auth/me and /api/auth/logout checked the
    session. Every other page and the whole management API -- add/delete
    firewall and NAT rules, WireGuard peers, interface up/down, backups,
    service start/stop -- answered anonymous requests

    So for a year after the “Complete Implementation” commit the web UI had no login that mattered and shipped with default passwords. Both are fixed: the admin password is per install (a random 24 character one written to a 0600 file on first start, and the service refuses to start with a known or short one), and a session is required for every page and API call, with regression tests.

    netlab

    There’s a harness called netlab: network namespaces, a router and clients and an ISP, real nftables and real daemons, scenarios as shell scripts, and power-cut and upgrade tests. A README claims ledger maps every claim to a test that proves it, and a “honesty guard” test scans the web code for old fixture values.

    CI-only failures: sh -c with bash functions (dash can’t see them), a DNS resolver in the CI image binding port 53 before a scenario’s own, grep -q under pipefail killing nft with SIGPIPE, kernel logging that doesn’t reach a namespace, and a runner with br_netfilter loaded that made bridged traffic hit the firewall. Some scenarios couldn’t pass at all and were parked in a pending/ directory instead of leaving main red: UPnP port mapping (miniupnpd answered 501 on the nftables backend and I never solved it), DHCPv6 prefix delegation for a while, and VRF.

    From Forgejo, October 16 to November 6: 416 runs, 209 success, 108 failure, 82 cancelled. About 294 commits.

    Features, and what they were proven on

    Multi-WAN with failover, QoS (CAKE, fq_codel), DHCPv6 prefix delegation, OpenVPN, IPsec road warriors, VRFs, zero-touch provisioning, factory reset, LDAP login, GitOps, and per-device segmentation with DNS policy and time limits. The 0.2.0 CHANGELOG lists its own limits: unmanaged switches, IPv6 binding “next”, and simulated radios only. Most of this was proven in namespaces and one lab VM, not on a production network.

    The ISO and 0.2.0

    On October 27 Patronus got a live installer ISO using the same builder as Horcrux, then a text installer with no default password and key-only SSH, aarch64 images and a console menu. The v0.2.0 release on the 30th (“device segmentation (opt-in), per-device Wi-Fi, DNS rebinding, per-device DNS policy, time limits, insight history, uplink health”) needed five migrations. The first release.yml didn’t produce a release, so another commit made it do so, and fat LTO of the web crate was OOM-killed in a 6 GB runner slot so aarch64 uses thin LTO. The stable ISO gate also failed on stale red statuses from a superseded tag.

    Design decisions

    Headless first. After Horcrux’s rule “it should work on a headless server too, it shouldn’t be a desktop-only OS”, I made the same call for Patronus on November 3. A box is installed and recovered from the console, and operated over SSH and the web UI. There is no desktop edition and none is planned, and CI fails the build if a desktop or personal package lands in the image inputs. There’s also deliberately no private image variant: per-box data comes in via an answer file on the install USB and via GitOps.

    Encryption and boot: a firewall must never stop at boot waiting for a console passphrase. So the installer encrypts only if an unattended unlock works (a TPM2-sealed key, self-tested), and otherwise installs unencrypted and says so.

    ZFS by default with an XFS fallback below 8 GB (the disk page shows the reason). I want it for recovery, bit rot protection and resilience, and ext4 is never the default: Gentoo recommends XFS, and ZFS is just superior tech. There is no on-disk swap on a firewall.

    Opt-in segmentation: the release made it off by default, and an upgrade test asserts an upgraded box has isolation off.

    State on November 6

    The UI now requires a login, the installer passes a KVM test, and v0.2.0 is out with its limits written down.

    Other parts: Part 1, Part 2, Part 3, Part 4.

  • Patronus dev log 4: an installer test that failed 57 times in a row

    Patronus dev log 4: an installer test that failed 57 times in a row

    Part 4 in the Patronus dev log series.

    Backdated, written in November 2026 from git and CI records. Part 3 was the restart.

    The installer test

    I wanted a nightly test that installs Patronus into a real VM, with real ZFS, and reboots into it. On October 4 it went in as a workflow. It ran on a Mac build host through UTM, so an x86 guest was emulated on an ARM machine.

    It never worked. From the Forgejo record, October 1 to 15, nightly-realistic-installer-test.yml: 57 failures, 6 cancelled, 0 successes. The overall repo had 249 runs those two weeks: 87 success, 116 failure, 36 cancelled, 10 skipped.

    What the log looks like:

    • Oct 4 and 5: file handoff between the VM and the host. First assumed instant, then polled for 60 seconds, then 180.
    • Oct 5: a serial console capture finally showed where boot stuck. GRUB 2.12 showed its menu, printed “Booting `Linux lts’” and then nothing. I switched the CD-ROM from USB to IDE. The commit message calls it the most likely cause, not a proven one.
    • Oct 5: “retry the live-boot stage – confirmed non-deterministic”.
    • Oct 10: a failing screenshot step turned out to be macOS denying Screen Recording permission (TCC), not a product bug.
    • Oct 13: boot budget pushed to 2700 seconds and the job ceiling to 120 minutes.
    • Oct 14: “pause nightly installer test schedule (TCC emulation can’t finish in time)”, where TCC is a typo for TCG, QEMU’s software emulation.

    Part 5 is what replaced it.

    Bugs that mattered more

    Meanwhile, in the same two weeks:

    • The firewall validated nothing before building nft scripts, so a rule field could inject ruleset text (Oct 12). Fields are now validated first.
    • The web UI’s NAT rules changed an in-memory list and never reached nftables (Oct 12).
    • A literal interface name “any” or “all” was spliced into nft syntax (Oct 13).
    • Captive portal MAC addresses weren’t validated before use in nft commands (Oct 11).
    • Dashboard threat and alert counts were constants (42, 38 and 3) whatever the real state. A commit on the 14th derived them from real data and listed what was still a placeholder: model accuracy, health score and packet rate, “no real ML classifier or packet-rate counter exists yet to back them honestly”.
    • On Oct 13, the container image couldn’t run nft as a non-root user until it got CAP_NET_ADMIN and CAP_NET_RAW on the binary.

    Design decisions

    The commit that replaced the UTM setup says x86 under TCG never booted there.

    State on October 15

    CI on main in the fortnight: 66 successes and 43 failures.

    Other parts: Part 1, Part 2, Part 3, Part 5.

  • Patronus dev log 1: a firewall that claimed 100% feature parity on day one

    Patronus dev log 1: a firewall that claimed 100% feature parity on day one

    Part 1 in the Patronus dev log series.

    Backdated. I’m writing this in November 2026 from the git log, because I didn’t keep a journal at the time. It covers the first four days, October 8 to 11, 2025.

    Patronus is a firewall and router OS in Rust, aimed at Gentoo. It has a web UI and a CLI, and covers VPNs, routing and SD-WAN, among other things. Like Horcrux it’s Rust, for memory safety and compile time checks. I’ve used Gentoo since 2006 and I’ve done pentesting.

    Day one

    October 8 has 20 commits. The second one is titled “Patronus Firewall v0.1.0 – Complete Implementation”: 176 files, 51,695 insertions. Its message reads like a product launch:

    - 100% feature parity with pfSense/OPNsense
    - 10-100x faster performance (eBPF/XDP: 40-100 Gbps)
    - Enterprise-grade security (A+ rating, zero unsafe code)
    ...
    - 19 crates, ~45,000 LOC
    - Performance: <100μs latency, 1M+ concurrent connections

    I can’t find any benchmark, any test run or any CI result from that week that measures a single one of those numbers. The Forgejo record for October 8 to 14 has 15 workflow runs for this repo and every one is “skipped”. Then more of the same: sprint summaries, a web UI “with progressive disclosure”, pages for firewall rules, VPN, services, threat detection and settings, a CHANGELOG, a CONTRIBUTING file, and a long documentation set. Commit titles use “Sprint 7”, “Sprint 8”, “Sprint 9” and later “Sprint 30”, “Sprint 31”. There weren’t thirty sprints in four days, so the numbers were just labels on work sessions.

    Days two to four

    On the 9th: login and sessions, WireGuard QR codes for phone setup, WebSockets for live updates, a GitHub Pages site, and an SD-WAN design: per the commits, sites announce themselves with Ed25519 signatures and peer over WireGuard automatically, plus path monitoring and Kubernetes NetworkPolicy enforcement. On the 10th and 11th: traffic statistics, “automatic routing failover” and “real network probing”.

    Design decisions I can actually source

    The repo has a blog draft from October 9, 2025, “Why I Built Patronus”. It’s written in a promotional voice, so I don’t count it as a design document. Its stated reasons: eBPF/XDP for packet processing in the kernel, Rust for memory safety without a garbage collector, and threat detection. It claims a 100x throughput improvement over the traditional path. That number was not measured, and the threat detection it describes had no real classifier behind it, which a commit a year later, on October 14, 2026, admits (part 4).

    My own reason for Rust is memory safety and compile time checks.

    docs/GENTOO-INTEGRATION.md has a “Why Gentoo?” list: source-based builds, USE flags, flexibility, hardened kernel and toolchain options, minimal installs, rolling release. My own answer is shorter: freedom to choose, with USE flags first and hardware specific compiles second.

    State at October 11

    No measurements and no CI run that executed.

    Other parts: Part 2, Part 3, Part 4, Part 5.