Commit 24bf2df
committed
<Description>
The proxy middleware's WebSocket path currently sets `X-Forwarded-For` only
when the header is empty, dropping the proxy's own peer IP from the chain
whenever upstream proxies had already added entries. This breaks downstream
services that rely on the "rightmost untrusted" rule to extract the real
client IP, including echo's own `ExtractIPFromXFFHeader`.
The HTTP path delegates to `net/http/httputil.ReverseProxy`, which appends
`RemoteAddr` to the existing `X-Forwarded-For` chain — either inline in
`ServeHTTP`'s default Director path
([reverseproxy.go#L519-L531](https://github.com/golang/go/blob/go1.26.3/src/net/http/httputil/reverseproxy.go#L519-L531))
or via the explicit
[`(*ProxyRequest).SetXForwarded`](https://github.com/golang/go/blob/go1.26.3/src/net/http/httputil/reverseproxy.go#L82-L96)
when a `Rewrite` callback is configured. The WebSocket path uses `proxyRaw`,
which writes the request verbatim via `r.Write(out)`, so this middleware is
the only place where the appending happens for WebSocket Upgrade requests.
<Change>
Replace the "set if empty" guard with always-append. Read values via map
access to preserve multi-line `X-Forwarded-For` headers (RFC 9110 §5.3
allows combining them by joining values with commas).
<Test>
Added TestProxyWebSocketXForwardedFor exercising 4 cases:
- no incoming X-Forwarded-For → only c.RealIP()
- single-line single-entry → preserved + c.RealIP() at the tail
- ingle-line comma-separated → preserved + c.RealIP() at the tail
- multi-line headers (multiple X-Forwarded-For occurrences) → joined with , + c.RealIP() at the tail
Each case captures the request header at WebSocket Upgrade time inside the
upstream handler and asserts both the appended tail and the preserved prefix.
<Backwards compatibility>
The change is additive: existing entries are preserved and the proxy's own
peer IP is added at the tail. Downstream readers using the standard
"rightmost untrusted" rule (e.g. echo.ExtractIPFromXFFHeader) see no
behavioral difference for chains where they already worked, and start
returning the correct IP for chains where the proxy IP was previously
dropped.
<Background>
We hit this in production with an Echo-based WebSocket reverse proxy
fronting an internal service that uses echo.ExtractIPFromXFFHeader for
IP-based authorization. Multi-hop deployments (customer proxy → our reverse
proxy → backend) broke because the reverse proxy's egress IP was missing
from the chain reaching the backend.1 parent 29727ff commit 24bf2df
2 files changed
Lines changed: 131 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
355 | 355 | | |
356 | 356 | | |
357 | 357 | | |
358 | | - | |
359 | | - | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
360 | 366 | | |
361 | 367 | | |
362 | 368 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
| |||
1164 | 1165 | | |
1165 | 1166 | | |
1166 | 1167 | | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
| 1206 | + | |
| 1207 | + | |
| 1208 | + | |
| 1209 | + | |
| 1210 | + | |
| 1211 | + | |
| 1212 | + | |
| 1213 | + | |
| 1214 | + | |
| 1215 | + | |
| 1216 | + | |
| 1217 | + | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
| 1222 | + | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
| 1226 | + | |
| 1227 | + | |
| 1228 | + | |
| 1229 | + | |
| 1230 | + | |
| 1231 | + | |
| 1232 | + | |
| 1233 | + | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
| 1237 | + | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
| 1254 | + | |
| 1255 | + | |
| 1256 | + | |
| 1257 | + | |
| 1258 | + | |
| 1259 | + | |
| 1260 | + | |
| 1261 | + | |
| 1262 | + | |
| 1263 | + | |
| 1264 | + | |
| 1265 | + | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
| 1286 | + | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
0 commit comments