mirror of
https://github.com/juanfont/headscale.git
synced 2026-05-23 18:48:42 +09:00
noise: pin outer RemoteAddr onto tunnel requests
The HTTP/2 server inside the Noise tunnel fills r.RemoteAddr from the hijacked TCP socket, so /machine/register and /machine/map logged the reverse proxy's loopback peer (e.g. 127.0.0.1:44388) even with trusted_proxies set. The outer router's realIPMiddleware had already resolved the client IP onto req.RemoteAddr; that value never crossed the hijack. Replace the inner realIPMiddleware mount — dead inside the encrypted tunnel — with overrideRemoteAddr(req.RemoteAddr) so requests served over the tunnel report the outer-resolved client IP.
This commit is contained in:
@@ -159,9 +159,10 @@ func (h *Headscale) NoiseUpgradeHandler(
|
||||
}))
|
||||
r.Use(middleware.RequestID)
|
||||
|
||||
if h.realIPMiddleware != nil {
|
||||
r.Use(h.realIPMiddleware)
|
||||
}
|
||||
// The outer router resolved trusted_proxies on req before the
|
||||
// upgrade; pin that value across the hijack so /machine/* logs the
|
||||
// client IP instead of the reverse proxy's loopback peer.
|
||||
r.Use(overrideRemoteAddr(req.RemoteAddr))
|
||||
|
||||
r.Use(middleware.RequestLogger(&zerologRequestLogger{}))
|
||||
r.Use(middleware.Recoverer)
|
||||
@@ -294,6 +295,20 @@ func rejectUnsupported(
|
||||
return false
|
||||
}
|
||||
|
||||
// overrideRemoteAddr returns middleware that pins r.RemoteAddr to addr.
|
||||
// Used inside the Noise tunnel: the HTTP/2 server derives r.RemoteAddr
|
||||
// from the hijacked TCP socket (the reverse proxy's loopback peer), so
|
||||
// the outer request's resolved client IP must be carried across the
|
||||
// hijack boundary by hand.
|
||||
func overrideRemoteAddr(addr string) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r.RemoteAddr = addr
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *noiseServer) NotImplementedHandler(writer http.ResponseWriter, req *http.Request) {
|
||||
log.Trace().Caller().Str("path", req.URL.String()).Msg("not implemented handler hit")
|
||||
http.Error(writer, "Not implemented yet", http.StatusNotImplemented)
|
||||
|
||||
@@ -368,3 +368,31 @@ func TestSSHActionFollowUp_RejectsBindingMismatch(t *testing.T) {
|
||||
assert.Equal(t, http.StatusUnauthorized, rec.Code,
|
||||
"binding mismatch must be rejected with 401")
|
||||
}
|
||||
|
||||
// TestOverrideRemoteAddr asserts the middleware used inside the Noise
|
||||
// tunnel pins r.RemoteAddr to the value captured from the outer
|
||||
// (pre-hijack) request, so /machine/* requests log the trusted-proxy
|
||||
// resolved client IP instead of the hijacked TCP socket's loopback peer.
|
||||
func TestOverrideRemoteAddr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const clientAddr = "192.168.91.240"
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(overrideRemoteAddr(clientAddr))
|
||||
|
||||
var observed string
|
||||
|
||||
r.Get("/x", func(w http.ResponseWriter, r *http.Request) {
|
||||
observed = r.RemoteAddr
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/x", nil)
|
||||
req.RemoteAddr = "127.0.0.1:44388"
|
||||
|
||||
r.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
assert.Equal(t, clientAddr, observed)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user