@@ -11,6 +11,7 @@ import (
1111
1212 "github.com/stretchr/testify/require"
1313
14+ "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}
474475
476+ 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 ) {
477584 t .Parallel ()
0 commit comments