Path: blob/main/components/ws-proxy/pkg/proxy/workspacerouter_test.go
2500 views
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package proxy56import (7"net/http"8"net/http/httptest"9"net/url"10"testing"1112"github.com/gitpod-io/gitpod/ws-proxy/pkg/common"13"github.com/google/go-cmp/cmp"14"github.com/gorilla/mux"15)1617func TestWorkspaceRouter(t *testing.T) {18const wsHostRegex = "\\.ws\\.gitpod\\.dev"19const wsHostSuffix = ".ws.gitpod.dev"20type Expectation struct {21WorkspaceID string22WorkspacePort string23Status int24URL string25AdditionalHitCount int26DebugWorkspace string27}28tests := []struct {29Name string30URL string31Headers map[string]string32WSHostSuffix string33Router WorkspaceRouter34Infos []common.WorkspaceInfo35Expected Expectation36}{37{38Name: "host-based workspace access",39URL: "http://amaranth-smelt-9ba20cc1.ws.gitpod.dev/",40Headers: map[string]string{41forwardedHostnameHeader: "amaranth-smelt-9ba20cc1.ws.gitpod.dev",42},43Router: HostBasedRouter(forwardedHostnameHeader, wsHostSuffix, wsHostRegex),44WSHostSuffix: wsHostSuffix,45Expected: Expectation{46WorkspaceID: "amaranth-smelt-9ba20cc1",47Status: http.StatusOK,48URL: "http://amaranth-smelt-9ba20cc1.ws.gitpod.dev/",49},50},51{52Name: "host-based debug workspace access",53URL: "http://debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",54Headers: map[string]string{55forwardedHostnameHeader: "debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev",56},57Router: HostBasedRouter(forwardedHostnameHeader, wsHostSuffix, wsHostRegex),58WSHostSuffix: wsHostSuffix,59Expected: Expectation{60DebugWorkspace: "true",61WorkspaceID: "amaranth-smelt-9ba20cc1",62Status: http.StatusOK,63URL: "http://debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",64},65},66{67Name: "host-based port access",68URL: "http://1234-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",69Headers: map[string]string{70forwardedHostnameHeader: "1234-amaranth-smelt-9ba20cc1.ws.gitpod.dev",71},72Router: HostBasedRouter(forwardedHostnameHeader, wsHostSuffix, wsHostRegex),73WSHostSuffix: wsHostSuffix,74Expected: Expectation{75WorkspaceID: "amaranth-smelt-9ba20cc1",76WorkspacePort: "1234",77Status: http.StatusOK,78URL: "http://1234-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",79},80},81{82Name: "host-based debug port access",83URL: "http://1234-debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",84Headers: map[string]string{85forwardedHostnameHeader: "1234-debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev",86},87Router: HostBasedRouter(forwardedHostnameHeader, wsHostSuffix, wsHostRegex),88WSHostSuffix: wsHostSuffix,89Expected: Expectation{90DebugWorkspace: "true",91WorkspaceID: "amaranth-smelt-9ba20cc1",92WorkspacePort: "1234",93Status: http.StatusOK,94URL: "http://1234-debug-amaranth-smelt-9ba20cc1.ws.gitpod.dev/",95},96},97}9899for _, test := range tests {100t.Run(test.Name, func(t *testing.T) {101r := mux.NewRouter()102ideRouter, portRouter, blobserveRouter := test.Router(r, &fakeWsInfoProvider{infos: test.Infos})103var act Expectation104actRecorder := func(w http.ResponseWriter, req *http.Request) {105defer w.WriteHeader(http.StatusOK)106107vars := mux.Vars(req)108if vars == nil {109return110}111112act.WorkspaceID = vars[common.WorkspaceIDIdentifier]113act.WorkspacePort = vars[common.WorkspacePortIdentifier]114act.DebugWorkspace = vars[common.DebugWorkspaceIdentifier]115act.URL = req.URL.String()116act.AdditionalHitCount++117}118119if ideRouter != nil {120ideRouter.HandleFunc("/", actRecorder)121ideRouter.HandleFunc("/services", actRecorder)122}123if portRouter != nil {124portRouter.HandleFunc("/", actRecorder)125}126if blobserveRouter != nil {127blobserveRouter.HandleFunc("/", actRecorder)128blobserveRouter.HandleFunc("/image:version:/foo/main.js", actRecorder)129}130131// build artificial request132req, err := http.NewRequest("GET", test.URL, nil)133if err != nil {134t.Fatal(err)135}136for key, value := range test.Headers {137req.Header.Add(key, value)138}139140// "send" artificial request with response mock141rr := httptest.NewRecorder()142r.ServeHTTP(rr, req)143144// we don't count the first hit to make the expectation structure easier.145act.AdditionalHitCount--146147act.Status = rr.Code148149if diff := cmp.Diff(test.Expected, act); diff != "" {150t.Errorf("unexpected response (-want +got):\n%s", diff)151}152})153}154}155156func TestMatchWorkspaceHostHeader(t *testing.T) {157type matchResult struct {158MatchesWorkspace bool159MatchesPort bool160WorkspaceVars map[string]string161PortVars map[string]string162}163164wsHostSuffix := ".gitpod.io"165tests := []struct {166Name string167HostHeader string168Path string169Expected matchResult170}{171{172Name: "no match",173HostHeader: "foobar.com",174},175{176Name: "no host",177HostHeader: "",178},179{180Name: "no match 2",181HostHeader: "0d9rkrj560blqb5s07q431ru9mhg19k1k4bqgd1dbprtgmt7vuhk" + wsHostSuffix,182Path: "eu.gcr.io/gitpod-core-dev/build/ide/code:nightly@sha256:41aeea688aa0943bd746cb70c4ed378910f7c7ecf56f5f53ccb2b76c6b68e1a7/__files__/index.html",183},184{185Name: "no match 3",186HostHeader: "v--0d9rkrj560blqb5s07q431ru9mhg19k1k4bqgd1dbprtgmt7vuhk" + wsHostSuffix,187Path: "eu.gcr.io/gitpod-core-dev/build/ide/code:nightly@sha256:41aeea688aa0943bd746cb70c4ed378910f7c7ecf56f5f53ccb2b76c6b68e1a7/__files__/index.html",188},189{190Name: "workspace match",191HostHeader: "amaranth-smelt-9ba20cc1" + wsHostSuffix,192Expected: matchResult{193MatchesWorkspace: true,194WorkspaceVars: map[string]string{195common.WorkspaceIDIdentifier: "amaranth-smelt-9ba20cc1",196},197},198},199{200Name: "port match",201HostHeader: "8080-amaranth-smelt-9ba20cc1" + wsHostSuffix,202Expected: matchResult{203MatchesPort: true,204PortVars: map[string]string{205common.WorkspaceIDIdentifier: "amaranth-smelt-9ba20cc1",206common.WorkspacePortIdentifier: "8080",207},208},209},210}211for _, test := range tests {212t.Run(test.Name, func(t *testing.T) {213req := &http.Request{214Host: test.HostHeader,215URL: &url.URL{216Path: test.Path,217},218Method: http.MethodGet,219Header: http.Header{220forwardedHostnameHeader: []string{test.HostHeader},221},222}223224prov := func(req *http.Request) string { return test.HostHeader }225226wsMatch := mux.RouteMatch{Vars: make(map[string]string)}227matchesWS := matchWorkspaceHostHeader(wsHostSuffix, prov, false)(req, &wsMatch)228portMatch := mux.RouteMatch{Vars: make(map[string]string)}229matchesPort := matchWorkspaceHostHeader(wsHostSuffix, prov, true)(req, &portMatch)230res := matchResult{231MatchesPort: matchesPort,232MatchesWorkspace: matchesWS,233PortVars: portMatch.Vars,234WorkspaceVars: wsMatch.Vars,235}236if len(res.PortVars) == 0 {237res.PortVars = nil238}239if len(res.WorkspaceVars) == 0 {240res.WorkspaceVars = nil241}242243if diff := cmp.Diff(test.Expected, res); diff != "" {244t.Errorf("unexpected response (-want +got):\n%s", diff)245}246})247}248}249250func TestAcmeHandler(t *testing.T) {251type Expectation struct {252ContentType string253Code int254}255tests := []struct {256Name string257Method string258URL string259Body []byte260Expectation Expectation261}{262{263Name: "Valid acme request",264Method: http.MethodGet,265URL: "http://domain.example.com/.well-known/acme-challenge/token1",266Expectation: Expectation{267Code: 403,268ContentType: "text/plain; charset=utf-8",269},270},271{272Name: "Not an acme request",273Method: http.MethodGet,274URL: "http://domain.example.com/",275Expectation: Expectation{276Code: 404,277ContentType: "text/plain; charset=utf-8",278},279},280{281Name: "Valid acme request",282Method: http.MethodGet,283URL: "http://1.1.1.1/.well-known/acme-challenge/token1",284Expectation: Expectation{285Code: 403,286ContentType: "text/plain; charset=utf-8",287},288},289}290291for _, test := range tests {292t.Run(test.Name, func(t *testing.T) {293req, err := http.NewRequest(test.Method, test.URL, nil)294if err != nil {295t.Errorf("unexpected error:%v", err)296}297298w := httptest.NewRecorder()299300r := mux.NewRouter()301setupAcmeRouter(r)302303r.ServeHTTP(w, req)304305act := Expectation{306ContentType: w.Header().Get("Content-Type"),307Code: w.Code,308}309310if diff := cmp.Diff(test.Expectation, act); diff != "" {311t.Errorf("unexpected result (-want +got):\n%s", diff)312}313})314}315}316317318