Path: blob/main/components/ws-proxy/pkg/proxy/proxy.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"bytes"8"context"9"crypto/tls"10"errors"11stdlog "log"12"net/http"13"os"14"path/filepath"15"time"1617"github.com/gorilla/mux"18"github.com/klauspost/cpuid/v2"1920"github.com/gitpod-io/gitpod/common-go/log"21"github.com/gitpod-io/gitpod/ws-proxy/pkg/common"22"github.com/gitpod-io/gitpod/ws-proxy/pkg/sshproxy"23)2425// WorkspaceProxy is the entity which forwards all inbound requests to the relevant workspace pods.26type WorkspaceProxy struct {27Ingress HostBasedIngressConfig28Config Config29WorkspaceRouter WorkspaceRouter30WorkspaceInfoProvider common.WorkspaceInfoProvider31SSHGatewayServer *sshproxy.Server32}3334// NewWorkspaceProxy creates a new workspace proxy.35func NewWorkspaceProxy(ingress HostBasedIngressConfig, config Config, workspaceRouter WorkspaceRouter, workspaceInfoProvider common.WorkspaceInfoProvider, sshGatewayServer *sshproxy.Server) *WorkspaceProxy {36return &WorkspaceProxy{37Ingress: ingress,38Config: config,39WorkspaceRouter: workspaceRouter,40WorkspaceInfoProvider: workspaceInfoProvider,41SSHGatewayServer: sshGatewayServer,42}43}4445func redirectToHTTPS(w http.ResponseWriter, r *http.Request) {46target := "https://" + r.Host + r.URL.Path47if len(r.URL.RawQuery) > 0 {48target += "?" + r.URL.RawQuery49}50log.WithField("target", target).Debug("redirect to https")51http.Redirect(w, r, target, http.StatusTemporaryRedirect)52}5354// MustServe starts the proxy and ends the process if doing so fails.55func (p *WorkspaceProxy) MustServe(ctx context.Context) {56handler, err := p.Handler()57if err != nil {58log.WithError(err).Fatal("cannot initialize proxy - this is likely a configuration issue")59return60}6162httpServer := &http.Server{63Addr: p.Ingress.HTTPAddress,64Handler: http.HandlerFunc(redirectToHTTPS),65ErrorLog: stdlog.New(logrusErrorWriter{}, "", 0),66ReadTimeout: 1 * time.Second,67WriteTimeout: 1 * time.Second,68IdleTimeout: 0,69ReadHeaderTimeout: 2 * time.Second,70}7172httpServer.SetKeepAlivesEnabled(false)7374httpsServer := &http.Server{75Addr: p.Ingress.HTTPSAddress,76Handler: handler,77TLSConfig: &tls.Config{78CipherSuites: optimalDefaultCipherSuites(),79CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},80MinVersion: tls.VersionTLS12,81MaxVersion: tls.VersionTLS12,82PreferServerCipherSuites: true,83NextProtos: []string{"h2", "http/1.1"},84},85ErrorLog: stdlog.New(logrusErrorWriter{}, "", 0),86}8788var (89crt = p.Config.HTTPS.Certificate90key = p.Config.HTTPS.Key91)92if tproot := os.Getenv("TELEPRESENCE_ROOT"); tproot != "" {93crt = filepath.Join(tproot, crt)94key = filepath.Join(tproot, key)95}9697go func() {98err := httpServer.ListenAndServe()99if err != nil && !errors.Is(err, http.ErrServerClosed) {100log.WithError(err).Fatal("cannot start http proxy")101}102}()103104go func() {105err = httpsServer.ListenAndServeTLS(crt, key)106if err != nil && !errors.Is(err, http.ErrServerClosed) {107log.WithError(err).Fatal("cannot start proxy")108return109}110}()111112<-ctx.Done()113114shutDownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)115defer cancel()116117err = httpServer.Shutdown(shutDownCtx)118if err != nil {119log.WithError(err).Fatal("cannot stop HTTP server")120}121122err = httpsServer.Shutdown(shutDownCtx)123if err != nil {124log.WithError(err).Fatal("cannot stop HTTPS server")125}126}127128// Handler returns the HTTP handler that serves the proxy routes.129func (p *WorkspaceProxy) Handler() (http.Handler, error) {130r := mux.NewRouter()131132r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {133w.WriteHeader(http.StatusOK)134})135136// install routes137handlerConfig, err := NewRouteHandlerConfig(&p.Config, WithDefaultAuth(p.WorkspaceInfoProvider))138if err != nil {139return nil, err140}141ideRouter, portRouter, foreignRouter := p.WorkspaceRouter(r, p.WorkspaceInfoProvider)142err = installWorkspaceRoutes(ideRouter, handlerConfig, p.WorkspaceInfoProvider, p.SSHGatewayServer)143if err != nil {144return nil, err145}146err = installWorkspacePortRoutes(portRouter, handlerConfig, p.WorkspaceInfoProvider)147if err != nil {148return nil, err149}150err = installForeignRoutes(foreignRouter, handlerConfig, p.WorkspaceInfoProvider)151if err != nil {152return nil, err153}154return r, nil155}156157// cipher suites assuming AES-NI (hardware acceleration for AES).158var defaultCipherSuitesWithAESNI = []uint16{159tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,160tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,161tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,162tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,163tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,164tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,165}166167// defaultCipherSuites assuming lack of AES-NI (NO hardware acceleration for AES).168var defaultCipherSuitesWithoutAESNI = []uint16{169tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,170tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,171tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,172tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,173tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,174tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,175}176177// optimalDefaultCipherSuites returns an appropriate cipher178// suite to use depending on the hardware support for AES.179func optimalDefaultCipherSuites() []uint16 {180if cpuid.CPU.Supports(cpuid.AESNI) {181return defaultCipherSuitesWithAESNI182}183return defaultCipherSuitesWithoutAESNI184}185186var tlsHandshakeErrorPrefix = []byte("http: TLS handshake error")187188type logrusErrorWriter struct{}189190func (w logrusErrorWriter) Write(p []byte) (int, error) {191if bytes.Contains(p, tlsHandshakeErrorPrefix) {192return len(p), nil193}194195log.Errorf("%s", string(p))196return len(p), nil197}198199200