Skip to content

Stop emulating ProxyPreserveHost — migrate WO apps to X-Forwarded-Host #7

Description

@hugithordarson

Context

Modulo currently forwards the original {@code Host} header to the upstream WO app verbatim (see {@code ModuloProxy.addProxyHeaders}, look for the {@code FIXME} note). This emulates Apache's {@code ProxyPreserveHost On} directive.

The reason: WO apps — and wonder/ERX especially — read the {@code host} request header directly to derive {@code request.serverName()} and to generate absolute URLs. Without preserving the original host, they see modulo's upstream target (e.g. {@code hz1.rebbi.is:2008}) instead of the public hostname the browser asked for (e.g. {@code www.undirskriftasofnun.is}). Under Apache→modulo this happened to work, because Apache forwarded the original host with {@code ProxyPreserveHost On} and modulo's own host-rewriting was a second hop that left the X-Forwarded-* chain intact.

This is not what a modern reverse proxy is supposed to do. Jetty's default — and the RFC-aligned behavior — is to rewrite {@code Host} to the upstream's host:port, and signal the original through {@code X-Forwarded-Host} / {@code X-Forwarded-Proto} / {@code Forwarded} (RFC 7239). Modulo is short-circuiting that for compatibility.

What's wrong with the current shape

  • It bakes a behavioral assumption into modulo that's specific to legacy WO apps reading {@code host} directly.
  • It collides with anything downstream that expects {@code Host} to match the upstream's identity (TLS upstream connections that validate Host, virtual-host-aware upstreams, anything that cares about the conventional contract).
  • It silently masks the real {@code X-Forwarded-Host} mechanism. Apps that do use {@code X-Forwarded-Host} get no value from modulo because we never set one.

What we want

A clean migration path that ends with:

  1. Modulo emits {@code X-Forwarded-Host}, {@code X-Forwarded-For}, {@code X-Forwarded-Proto} (and/or RFC 7239 {@code Forwarded}) on every proxied request, populated correctly whether modulo is front-facing or behind another proxy.
  2. Modulo stops rewriting {@code Host} for the upstream — it goes through unchanged from Jetty's proxy default (which sets it to the upstream's host:port).
  3. WO/wonder is updated so {@code request.serverName()} and friends prefer {@code X-Forwarded-Host} over {@code Host} when present.

Step 3 is the gating item — until WO/wonder honors X-Forwarded-Host, modulo can't drop the workaround without breaking apps.

Migration sketch

  • Add {@code X-Forwarded-*} emission in modulo (does no harm even with the host-preserve workaround still in place).
  • Patch wonder/ERX (or its slim equivalent) to honor {@code X-Forwarded-Host} in {@code WORequest.serverName()} and absolute-URL generation. Probably small.
  • Deploy updated wonder to apps.
  • Once apps are on the updated wonder, drop the {@code Host}-preserve override in {@code ModuloProxy} and let Jetty's default behavior take over.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions