Comparing route-proxy against what HAL's actual Traefik config does today, not Traefik's general feature set, per the standing rule that the nox mesh must do at minimum what the HAL mesh it replaces already does. Everything else checked out even or better; these two are real, confirmed gaps — RedisInsight has no login of its own and depends entirely on Traefik's basicauth middleware, and the gitea-internal route depends on an IP-scoped deny rule. Neither has any equivalent in route-proxy's single-lookup request path.
4.3 KiB
status, opened, located-in, fixed-by, amended-design
| status | opened | located-in | fixed-by | amended-design | |
|---|---|---|---|---|---|
| located | 2026-09-25 |
|
116 — route-proxy has no authentication or IP-restriction mechanism, and two live HAL routes depend on one
What was observed
On the control-node, 2026-09-25, deciding whether route-proxy (novox/hq 08-connectivity §3,
mesh-controller/examples/route-proxy/main.go) can replace HAL's adopted Traefik instance as the
permanent public ingress. The standing requirement is that the nox mesh does, at minimum, what the
HAL mesh it is replacing already does — so the comparison is against Traefik's current, real
configuration on this node, not Traefik's general feature set.
Reading /services/traefik/dynamic/ on the control-node directly, two of its routes carry
middleware route-proxy has nothing to match:
redisinsight.novox.be → traefik.http.middlewares.redisinsight-auth.basicauth.users: <htpasswd hash>
(RedisInsight has no login of its own; this is the only gate in front of it)
gitea-internal → zz-security-gitea-internal-deny.yml — an IP-scoped deny rule
route-proxy's entire request path is main.go's handler(): one lookup into the routing table
built from $ROUTES, then proxy.ServeHTTP(w, r) — no middleware chain, no auth check, no source
filtering, anywhere in the file. Confirmed by reading the whole of main.go (261 lines): the only
things it does per-request are route lookup and proxy. It was designed this way deliberately — "a
route hands back a name, not a credential" (the module's own README) — but that design covers the
route grant, not what a request arriving at the name is allowed to do, which is what these two
HAL routes need.
Everything else compared cleanly or better, and is not part of this issue:
- Streaming/large request bodies (why Traefik needed
minio-api-body-size: maxRequestBodyBytes: 0for the object store's routes):net/http/httputil.ReverseProxystreams with no default cap, so this need requires nothing extra here. - WebSocket/connection upgrades (used by at least one console route): native in
httputil.ReverseProxysince Go 1.12; not yet verified live against this proxy, but not absent by design the way auth is. - TLS/ACME:
route-proxy's ownHostPolicy(onlyWhatTheMeshSaid) refuses to certify anything the mesh did not actually route, and defaults to Let's Encrypt staging until a node opts in to production (novox/hq 04-ISSUES/004) — stricter than the hand-maintained Traefik config it would replace. - Multiple public names for one module (minio's
files-api/files): already solved by thecontributesmany-shape (mesh-controller PR #55), needs nothing further from the proxy.
Why it matters
Two routes cannot move off Traefik until this is closed, and — under the standing "at minimum" rule — nothing wholesale can be called a Traefik replacement while it is. Left unfixed, either:
- those two routes stay on
route-adapter/Traefik indefinitely, which means the mesh is running two reverse proxies side by side for no principled reason, or - somebody migrates them anyway and RedisInsight (no auth of its own) or the gitea-internal path (currently IP-restricted) becomes reachable to anyone who can resolve the name — a real exposure, not a cosmetic gap.
Open questions
- Where does the auth/restriction config come from? The contract today is "one contribution, a
name and a port" (
main.go'scontribution.Values) — extending it to optionally carry an htpasswd hash or an allowed-CIDR list keeps the mesh (not the proxy) as the source of truth, consistent with everything elseroutealready does. An alternative — a second, proxy-specific settings layer, keyed by route name — decouples it from the route grant itself but adds a second place to look. Worth deciding deliberately rather than defaulting to whichever is less code. - Basic auth and an IP allow/deny list cover what HAL's config uses today. Whether that is the right general shape for route-level middleware, or just the two cases observed so far, is itself worth a decision before writing it — the module's own README already states "the contract is the file, not this program," so nothing here requires all of it to live in this one reference implementation.