fix!: only trust x-forwarded-host from configured trusted proxies (#2… · coder/coder@a992c2c
@@ -11,6 +11,7 @@ import (
11111212"github.com/stretchr/testify/require"
131314+"github.com/coder/coder/v2/coderd/httpapi"
1415"github.com/coder/coder/v2/coderd/httpmw"
1516)
1617@@ -472,6 +473,112 @@ func TestFilterUntrusted(t *testing.T) {
472473 }
473474}
474475476+func TestEffectiveHost(t *testing.T) {
477+t.Parallel()
478+479+cidr32 := func(t *testing.T, ip string) *net.IPNet {
480+t.Helper()
481+482+return &net.IPNet{
483+IP: net.ParseIP(ip),
484+Mask: net.CIDRMask(32, 32),
485+ }
486+ }
487+488+t.Run("UntrustedPeerFallsBackToReceivedHost", func(t *testing.T) {
489+t.Parallel()
490+491+r := httptest.NewRequest(http.MethodGet, "http://received.test", nil)
492+r.RemoteAddr = "17.18.19.20:1234"
493+r.Header.Set(httpapi.XForwardedHostHeader, "app.test.coder.com")
494+495+require.Equal(t, "received.test", httpmw.EffectiveHost(nil, r))
496+ })
497+498+t.Run("TrustedPeerUsesOriginalRemoteAddrForTrust", func(t *testing.T) {
499+t.Parallel()
500+501+config := &httpmw.RealIPConfig{
502+TrustedOrigins: []*net.IPNet{cidr32(t, "17.18.19.20")},
503+TrustedHeaders: []string{"X-Real-Ip"},
504+ }
505+506+r := httptest.NewRequest(http.MethodGet, "http://received.test", nil)
507+r.RemoteAddr = "17.18.19.20:1234"
508+// X-Real-Ip causes ExtractRealIP to rewrite r.RemoteAddr, so
509+// this test can verify trust still uses OriginalRemoteAddr,
510+// the actual socket peer.
511+r.Header.Set("X-Real-Ip", "99.88.77.66")
512+r.Header.Set(httpapi.XForwardedHostHeader, "app.test.coder.com")
513+514+middleware := httpmw.ExtractRealIP(config)
515+next := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
516+require.Equal(t, "99.88.77.66", r.RemoteAddr)
517+require.Equal(t, "app.test.coder.com", httpmw.EffectiveHost(config, r))
518+ })
519+520+middleware(next).ServeHTTP(httptest.NewRecorder(), r)
521+ })
522+523+t.Run("UntrustedPeerDoesNotHonorForwardedHost", func(t *testing.T) {
524+t.Parallel()
525+526+config := &httpmw.RealIPConfig{
527+TrustedOrigins: []*net.IPNet{cidr32(t, "99.88.77.66")},
528+TrustedHeaders: []string{"X-Real-Ip"},
529+ }
530+531+r := httptest.NewRequest(http.MethodGet, "http://received.test", nil)
532+r.RemoteAddr = "17.18.19.20:1234"
533+r.Header.Set("X-Real-Ip", "99.88.77.66")
534+r.Header.Set(httpapi.XForwardedHostHeader, "app.test.coder.com")
535+536+middleware := httpmw.ExtractRealIP(config)
537+nextHandler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
538+require.Equal(t, "17.18.19.20", r.RemoteAddr)
539+require.Equal(t, "received.test", httpmw.EffectiveHost(config, r))
540+ })
541+542+middleware(nextHandler).ServeHTTP(httptest.NewRecorder(), r)
543+ })
544+545+t.Run("TrustedPeerWithoutForwardedHostFallsBackToReceivedHost", func(t *testing.T) {
546+t.Parallel()
547+548+config := &httpmw.RealIPConfig{
549+TrustedOrigins: []*net.IPNet{cidr32(t, "17.18.19.20")},
550+TrustedHeaders: []string{"X-Real-Ip"},
551+ }
552+553+r := httptest.NewRequest(http.MethodGet, "http://received.test", nil)
554+r.RemoteAddr = "17.18.19.20:1234"
555+556+middleware := httpmw.ExtractRealIP(config)
557+nextHandler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
558+require.Equal(t, "received.test", httpmw.EffectiveHost(config, r))
559+ })
560+561+middleware(nextHandler).ServeHTTP(httptest.NewRecorder(), r)
562+ })
563+564+t.Run("MalformedRemoteAddrFallsBackToReceivedHost", func(t *testing.T) {
565+t.Parallel()
566+567+config := &httpmw.RealIPConfig{
568+TrustedOrigins: []*net.IPNet{cidr32(t, "17.18.19.20")},
569+TrustedHeaders: []string{"X-Real-Ip"},
570+ }
571+572+r := httptest.NewRequest(http.MethodGet, "http://received.test", nil)
573+// A RemoteAddr that cannot be parsed into an IP must be treated as
574+// untrusted, so the forwarded host is ignored.
575+r.RemoteAddr = "garbage"
576+r.Header.Set(httpapi.XForwardedHostHeader, "app.test.coder.com")
577+578+require.Equal(t, "received.test", httpmw.EffectiveHost(config, r))
579+ })
580+}
581+475582// TestApplicationProxy checks headers passed to DevURL services are as expected.
476583func TestApplicationProxy(t *testing.T) {
477584t.Parallel()