feat(resources): add custom request and response headers #6

Merged
ryan.mcgee merged 26 commits from feat/custom-headers into main 2026-10-15 18:07:01 +00:00
Owner

Summary

  • HTTP resources can add custom headers to requests sent to the targets or to responses returned to visitors; a header with no value is removed.
  • Stacked on #5 (feat/path-routing); retarget to main once #2 to #5 are merged.

Changes

  • New resource_headers table (reversible, portable): up to 50 per resource, unique per resource, direction and canonical name.
  • Header is the single definition of valid headers, shared by the changeset and the renderer: names are HTTP tokens stored in canonical case (so x-a and X-A cannot both exist); values may not contain line breaks or control characters; framing and hop-by-hop headers are refused; Remote-User and Remote-Email cannot be set on requests because access control uses them to tell the backend who signed in.
  • Resources.add_header/3, get_header!/2, delete_header/2: admin-only, {:error, :not_http} for TCP/UDP resources, tenant-scoped.
  • DynamicConfig renders one headers middleware per resource on every router of the resource, chained after forwardAuth and before the path rewrite; invalid stored rows are dropped.
  • UI: a Headers section on the resource page (admins add and remove; members read), with inline errors.
  • README section.

Verification

  • 584 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: all other tables unchanged (the headers table is dropped and recreated, so header rows do not survive a rollback); SQLite integrity and foreign-key checks clean.
  • Real Traefik: everything loaded with zero errors; request headers set and a visitor-sent value replaced; a header removed from the request; response headers received by the visitor; on a protected resource the chain is auth then headers, and denied requests (403) leak nothing; a visitor-sent Remote-User does not reach the backend. Observed: setting Host on a request changes the host the backend sees (documented, and pinned by a test). Added and refused headers also checked in the browser.
  • Not exercised live: removing a response header (covered by renderer tests only).

Not included

  • Editing a header in place (remove and re-add).
## Summary - HTTP resources can add custom headers to requests sent to the targets or to responses returned to visitors; a header with no value is removed. - Stacked on #5 (`feat/path-routing`); retarget to `main` once #2 to #5 are merged. ## Changes - New `resource_headers` table (reversible, portable): up to 50 per resource, unique per resource, direction and canonical name. - `Header` is the single definition of valid headers, shared by the changeset and the renderer: names are HTTP tokens stored in canonical case (so `x-a` and `X-A` cannot both exist); values may not contain line breaks or control characters; framing and hop-by-hop headers are refused; `Remote-User` and `Remote-Email` cannot be set on requests because access control uses them to tell the backend who signed in. - `Resources.add_header/3`, `get_header!/2`, `delete_header/2`: admin-only, `{:error, :not_http}` for TCP/UDP resources, tenant-scoped. - `DynamicConfig` renders one `headers` middleware per resource on every router of the resource, chained after forwardAuth and before the path rewrite; invalid stored rows are dropped. - UI: a Headers section on the resource page (admins add and remove; members read), with inline errors. - README section. ## Verification - 584 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: all other tables unchanged (the headers table is dropped and recreated, so header rows do not survive a rollback); SQLite integrity and foreign-key checks clean. - Real Traefik: everything loaded with zero errors; request headers set and a visitor-sent value replaced; a header removed from the request; response headers received by the visitor; on a protected resource the chain is auth then headers, and denied requests (403) leak nothing; a visitor-sent `Remote-User` does not reach the backend. Observed: setting `Host` on a request changes the host the backend sees (documented, and pinned by a test). Added and refused headers also checked in the browser. - Not exercised live: removing a response header (covered by renderer tests only). ## Not included - Editing a header in place (remove and re-add).
- HTTP resources hold up to 50 headers, each set on requests to the
  targets or on responses to visitors; a header with no value is
  removed instead
- names are validated tokens stored in canonical case, so one header
  cannot be defined twice; values may not contain line breaks or
  control characters
- framing and hop-by-hop headers are refused, and so are the
  Remote-User and Remote-Email request headers that access control sets
- one headers middleware per resource, running after forwardAuth and
  before the path rewrite; invalid stored rows are dropped
- a Headers section on the resource page for admins
ryan.mcgee changed target branch from feat/path-routing to main 2026-10-15 18:06:54 +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!6
No description provided.