◐ Shell
clean mode source ↗

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.

476583

func TestApplicationProxy(t *testing.T) {

477584

t.Parallel()