feat(resources): add path routing and sticky sessions #5

Merged
ryan.mcgee merged 25 commits from feat/path-routing into main 2026-10-15 18:06:54 +00:00
Owner

Summary

  • Targets of an HTTP resource can carry a path route (prefix, exact or regex match) and a path rewrite, so one domain can split paths across backends; resources get optional sticky sessions.
  • Stacked on #4 (feat/tcp-udp); retarget to main once #2, #3 and #4 are merged.

Changes

  • Migration adds sticky_sessions to resources and path_match, path, rewrite, rewrite_to to targets. Columns only, so it is portable and reversible.
  • PathRoute is the single definition of valid routes, shared by the changeset and the renderer: URL path characters only (no backtick, so a path can never break out of a Traefik rule and reach another tenant's host), regexes must compile and may not use features Traefik's RE2 lacks. A path without a match type is an error, never a silent catch-all.
  • Targets sharing a path must share its rewrite (refused otherwise); targets with the same route load-balance together.
  • DynamicConfig renders one router, service and rewrite middleware per route. The catch-all keeps its names and output, so existing configs do not change. Path routers get explicit priorities (exact, then regex, then longer prefixes) and always include the resource's own host. The rewrite runs after forwardAuth, so access rules see the original path. One unprotected redirect router covers the whole host. Invalid or conflicting stored routes are dropped (lowest target id wins, in any order).
  • Sticky resources get a per-service cookie (tcs_<hash>, HttpOnly, SameSite=Lax, Secure over TLS); always off for TCP/UDP.
  • UI: a match select that reveals the path, rewrite and replacement fields; a Route column; a sticky checkbox and badge. Unknown form values count as blank instead of crashing the page.
  • README section and status update.

Verification

  • 545 tests pass on SQLite and PostgreSQL; format, compile --warnings-as-errors, credo --strict clean; mix dialyzer 0 errors on both backends.
  • Migration up, down, up on populated databases on both backends: row counts unchanged, SQLite integrity and foreign-key checks clean; also applied to the existing populated dev database.
  • Real Traefik (compose stack): all routers, services and middlewares loaded with zero errors; strip prefix, prefix replace, regex replace and exact replace rewrite as configured; exact beats prefix; /api/ does not match /apix while /ui matches /uix; a deny rule on /api/admin* still returns 403 on a resource whose /api/ prefix is stripped (also for upper-case and percent-encoded forms); other hosts unaffected; disabling a route in the browser falls back to the catch-all.
  • Sticky: requests alternate between two backends without the cookie, stay on one with it, and different visitors can land on different backends.

Not included

  • Editing a target in place (remove and re-add), custom headers, backend TLS options.
## Summary - Targets of an HTTP resource can carry a path route (prefix, exact or regex match) and a path rewrite, so one domain can split paths across backends; resources get optional sticky sessions. - Stacked on #4 (`feat/tcp-udp`); retarget to `main` once #2, #3 and #4 are merged. ## Changes - Migration adds `sticky_sessions` to resources and `path_match`, `path`, `rewrite`, `rewrite_to` to targets. Columns only, so it is portable and reversible. - `PathRoute` is the single definition of valid routes, shared by the changeset and the renderer: URL path characters only (no backtick, so a path can never break out of a Traefik rule and reach another tenant's host), regexes must compile and may not use features Traefik's RE2 lacks. A path without a match type is an error, never a silent catch-all. - Targets sharing a path must share its rewrite (refused otherwise); targets with the same route load-balance together. - `DynamicConfig` renders one router, service and rewrite middleware per route. The catch-all keeps its names and output, so existing configs do not change. Path routers get explicit priorities (exact, then regex, then longer prefixes) and always include the resource's own host. The rewrite runs after forwardAuth, so access rules see the original path. One unprotected redirect router covers the whole host. Invalid or conflicting stored routes are dropped (lowest target id wins, in any order). - Sticky resources get a per-service cookie (`tcs_<hash>`, HttpOnly, SameSite=Lax, Secure over TLS); always off for TCP/UDP. - UI: a match select that reveals the path, rewrite and replacement fields; a Route column; a sticky checkbox and badge. Unknown form values count as blank instead of crashing the page. - README section and status update. ## Verification - 545 tests pass on SQLite and PostgreSQL; format, `compile --warnings-as-errors`, `credo --strict` clean; `mix dialyzer` 0 errors on both backends. - Migration up, down, up on populated databases on both backends: row counts unchanged, SQLite integrity and foreign-key checks clean; also applied to the existing populated dev database. - Real Traefik (compose stack): all routers, services and middlewares loaded with zero errors; strip prefix, prefix replace, regex replace and exact replace rewrite as configured; exact beats prefix; `/api/` does not match `/apix` while `/ui` matches `/uix`; a deny rule on `/api/admin*` still returns 403 on a resource whose `/api/` prefix is stripped (also for upper-case and percent-encoded forms); other hosts unaffected; disabling a route in the browser falls back to the catch-all. - Sticky: requests alternate between two backends without the cookie, stay on one with it, and different visitors can land on different backends. ## Not included - Editing a target in place (remove and re-add), custom headers, backend TLS options.
- targets of HTTP resources can carry a path match (prefix, exact or
  regex) and a rewrite (strip prefix, replace prefix, replace path or
  regex replace); targets that share a path must share its rewrite
- PathRoute is the single definition of valid paths: URL path
  characters only, so a path can never break out of a router rule, and
  regexes must compile and avoid features Traefik's RE2 lacks
- a path without a match type is an error rather than a silent
  catch-all
- resources get a sticky_sessions flag, always off for TCP and UDP
- the migration only adds columns, so it is portable and reversible
- each distinct path route of an HTTP resource gets its own router,
  service and rewrite middleware; the catch-all keeps its names and
  output, so existing configs do not change
- path routers get an explicit priority (exact, then regex, then
  longer prefixes) and always include the resource's own host
- the rewrite runs after forwardAuth, so access rules see the
  original path
- one unprotected redirect router covers the whole host
- invalid or conflicting stored routes are dropped, the lowest
  target id winning, whatever order targets arrive in
- sticky resources set a per-service cookie (HttpOnly, SameSite=Lax,
  Secure over TLS)
- the target form picks a match (any path, prefix, exact, regex) and
  shows the path, rewrite and replacement fields that apply to it
- the targets table shows each target's route, and sticky resources
  carry a badge
- sticky sessions are a checkbox on HTTP resources
- unknown form values count as blank instead of crashing the page
- match and rewrite types, priorities, per-route load balancing and
  the one-rewrite-per-path rule
- prefix semantics, RE2 syntax, rewrite after access control
- sticky cookie attributes and limits
ryan.mcgee changed target branch from feat/tcp-udp to main 2026-10-15 18:06:44 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
White_Ghost_Cyber_Systems/traefik-control!5
No description provided.