Wrap each backend RoundTripper returned by TransportManager with a thin zapBackendRoundTripper that dials selected upstreams via github.com/zap-proto/http instead of net/http. Allowlist comes from env INGRESS_ZAP_BACKENDS (comma-separated host:port). Empty/unset env keeps the wrapped transport untouched, so the change is purely additive. External (client-facing) TLS termination is unchanged. CRD schema is unchanged. One *zaphttp.Transport per backend addr (zaphttp pins to a single peer and ignores req.URL.Host). Adds reference doc with IngressRoute example and notes LLM.md.
3.7 KiB
LLM.md - Hanzo Ingress
Overview
Go module: github.com/hanzoai/ingress/v3
Tech Stack
- Language: Go
Build & Run
go build ./...
go test ./...
Structure
ingress/
CHANGELOG.md
CODE_OF_CONDUCT.md
CONTRIBUTING.md
Dockerfile
Dockerfile.bin
LICENSE.md
LLM.md
Makefile
README.md
SECURITY.md
cmd/
contrib/
docs/
generate.go
go.mod
Key Files
README.md-- Project documentationgo.mod-- Go module definitionMakefile-- Build automationDockerfile-- Container build
Rebrand Notes (v3.8.0)
External library constraints
github.com/traefik/paerserdefinesDefaultRootName = "traefik"andDefaultNamePrefix = "TRAEFIK_". Config file flags (-traefik.configfile), env var prefix (TRAEFIK_*), and parser root name must stay astraefikto match the external library. These are NOT candidates for rebrand.github.com/traefik/yaegi,github.com/traefik/grpc-web,github.com/traefik/oxyare external imports -- never touch their paths.
Hash-bound test data
pkg/middlewares/auth/digest_auth_test.gocontains htdigest hashes computed asmd5(user:realm:password)where realm="traefik". Changing the realm invalidates the hash. These 10 references must stay.
Wire protocol changes (intentional)
X-Traefik-Fast-Proxyheader renamed toX-Ingress-Fast-ProxyX-Traefik-Routerheader renamed toX-Ingress-Router- Prometheus/InfluxDB metric prefix:
traefik.*renamed toingress.*
Pre-existing test failures (not caused by rebrand)
pkg/muxer/http/Test_addRoute/Host_IPv6: Go 1.26 broke IPv6 URL parsingpkg/middlewares/ratelimiter: Timing-sensitive tests, flaky
Header Passthrough Behavior (2026-04-13)
The ingress controller correctly passes through ALL backend response headers.
Both the httputil.ReverseProxy path and the fast proxy path copy response
headers verbatim via VisitAll / Header().Add().
The security-headers Middleware CRD (k8s/hanzo/middlewares.yaml) uses
unrolled/secure to ADD secure headers (HSTS, X-Frame-Options, etc.) via
ModifyResponseHeaders. It uses res.Header.Set(), which overwrites same-named
backend headers. This is intentional -- ingress-level security headers override
backend headers.
The contenttype.DisableAutoDetection wrapper on the entrypoint sets
Content-Type to nil in the header map before handler execution. This prevents
Go's default content-type sniffing but does not strip any other headers.
No code in the ingress adds Content-Disposition. If you see
Content-Disposition: inline; filename="index.html" in responses, it comes from
the backend (Go's http.ServeContent or http.ServeFile).
Annotation prefix
The K8s Ingress provider uses annotation prefix ingress.kubernetes.io/, NOT
traefik.ingress.kubernetes.io/. Annotations with the old Traefik prefix are
silently ignored.
ZAP-HTTP Backend Transport
pkg/server/service/zap_backend.go wraps every backend RoundTripper
returned by TransportManager.createRoundTripper. When the request's
upstream host:port (i.e. req.URL.Host at transport time) matches the
allowlist from env INGRESS_ZAP_BACKENDS (comma-separated), the request
is dialed via github.com/zap-proto/http instead of net/http. All
other backends fall through unchanged. Empty / unset env keeps the
wrapped transport untouched (no allocation, no overhead).
zaphttp.NewTransport pins to one peer (it ignores req.URL.Host
internally), so we keep one *zaphttp.Transport per backend address in
a sync.Map.
External (client-facing) TLS termination is untouched. CRD schema is
untouched. Reference doc:
docs/content/reference/dynamic-configuration/zap-backend.md.