Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-proxy/pkg/proxy/proxy.go
2500 views
1
// Copyright (c) 2020 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package proxy
6
7
import (
8
"bytes"
9
"context"
10
"crypto/tls"
11
"errors"
12
stdlog "log"
13
"net/http"
14
"os"
15
"path/filepath"
16
"time"
17
18
"github.com/gorilla/mux"
19
"github.com/klauspost/cpuid/v2"
20
21
"github.com/gitpod-io/gitpod/common-go/log"
22
"github.com/gitpod-io/gitpod/ws-proxy/pkg/common"
23
"github.com/gitpod-io/gitpod/ws-proxy/pkg/sshproxy"
24
)
25
26
// WorkspaceProxy is the entity which forwards all inbound requests to the relevant workspace pods.
27
type WorkspaceProxy struct {
28
Ingress HostBasedIngressConfig
29
Config Config
30
WorkspaceRouter WorkspaceRouter
31
WorkspaceInfoProvider common.WorkspaceInfoProvider
32
SSHGatewayServer *sshproxy.Server
33
}
34
35
// NewWorkspaceProxy creates a new workspace proxy.
36
func NewWorkspaceProxy(ingress HostBasedIngressConfig, config Config, workspaceRouter WorkspaceRouter, workspaceInfoProvider common.WorkspaceInfoProvider, sshGatewayServer *sshproxy.Server) *WorkspaceProxy {
37
return &WorkspaceProxy{
38
Ingress: ingress,
39
Config: config,
40
WorkspaceRouter: workspaceRouter,
41
WorkspaceInfoProvider: workspaceInfoProvider,
42
SSHGatewayServer: sshGatewayServer,
43
}
44
}
45
46
func redirectToHTTPS(w http.ResponseWriter, r *http.Request) {
47
target := "https://" + r.Host + r.URL.Path
48
if len(r.URL.RawQuery) > 0 {
49
target += "?" + r.URL.RawQuery
50
}
51
log.WithField("target", target).Debug("redirect to https")
52
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
53
}
54
55
// MustServe starts the proxy and ends the process if doing so fails.
56
func (p *WorkspaceProxy) MustServe(ctx context.Context) {
57
handler, err := p.Handler()
58
if err != nil {
59
log.WithError(err).Fatal("cannot initialize proxy - this is likely a configuration issue")
60
return
61
}
62
63
httpServer := &http.Server{
64
Addr: p.Ingress.HTTPAddress,
65
Handler: http.HandlerFunc(redirectToHTTPS),
66
ErrorLog: stdlog.New(logrusErrorWriter{}, "", 0),
67
ReadTimeout: 1 * time.Second,
68
WriteTimeout: 1 * time.Second,
69
IdleTimeout: 0,
70
ReadHeaderTimeout: 2 * time.Second,
71
}
72
73
httpServer.SetKeepAlivesEnabled(false)
74
75
httpsServer := &http.Server{
76
Addr: p.Ingress.HTTPSAddress,
77
Handler: handler,
78
TLSConfig: &tls.Config{
79
CipherSuites: optimalDefaultCipherSuites(),
80
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
81
MinVersion: tls.VersionTLS12,
82
MaxVersion: tls.VersionTLS12,
83
PreferServerCipherSuites: true,
84
NextProtos: []string{"h2", "http/1.1"},
85
},
86
ErrorLog: stdlog.New(logrusErrorWriter{}, "", 0),
87
}
88
89
var (
90
crt = p.Config.HTTPS.Certificate
91
key = p.Config.HTTPS.Key
92
)
93
if tproot := os.Getenv("TELEPRESENCE_ROOT"); tproot != "" {
94
crt = filepath.Join(tproot, crt)
95
key = filepath.Join(tproot, key)
96
}
97
98
go func() {
99
err := httpServer.ListenAndServe()
100
if err != nil && !errors.Is(err, http.ErrServerClosed) {
101
log.WithError(err).Fatal("cannot start http proxy")
102
}
103
}()
104
105
go func() {
106
err = httpsServer.ListenAndServeTLS(crt, key)
107
if err != nil && !errors.Is(err, http.ErrServerClosed) {
108
log.WithError(err).Fatal("cannot start proxy")
109
return
110
}
111
}()
112
113
<-ctx.Done()
114
115
shutDownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
116
defer cancel()
117
118
err = httpServer.Shutdown(shutDownCtx)
119
if err != nil {
120
log.WithError(err).Fatal("cannot stop HTTP server")
121
}
122
123
err = httpsServer.Shutdown(shutDownCtx)
124
if err != nil {
125
log.WithError(err).Fatal("cannot stop HTTPS server")
126
}
127
}
128
129
// Handler returns the HTTP handler that serves the proxy routes.
130
func (p *WorkspaceProxy) Handler() (http.Handler, error) {
131
r := mux.NewRouter()
132
133
r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
134
w.WriteHeader(http.StatusOK)
135
})
136
137
// install routes
138
handlerConfig, err := NewRouteHandlerConfig(&p.Config, WithDefaultAuth(p.WorkspaceInfoProvider))
139
if err != nil {
140
return nil, err
141
}
142
ideRouter, portRouter, foreignRouter := p.WorkspaceRouter(r, p.WorkspaceInfoProvider)
143
err = installWorkspaceRoutes(ideRouter, handlerConfig, p.WorkspaceInfoProvider, p.SSHGatewayServer)
144
if err != nil {
145
return nil, err
146
}
147
err = installWorkspacePortRoutes(portRouter, handlerConfig, p.WorkspaceInfoProvider)
148
if err != nil {
149
return nil, err
150
}
151
err = installForeignRoutes(foreignRouter, handlerConfig, p.WorkspaceInfoProvider)
152
if err != nil {
153
return nil, err
154
}
155
return r, nil
156
}
157
158
// cipher suites assuming AES-NI (hardware acceleration for AES).
159
var defaultCipherSuitesWithAESNI = []uint16{
160
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
161
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
162
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
163
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
164
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
165
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
166
}
167
168
// defaultCipherSuites assuming lack of AES-NI (NO hardware acceleration for AES).
169
var defaultCipherSuitesWithoutAESNI = []uint16{
170
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
171
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
172
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
173
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
174
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
175
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
176
}
177
178
// optimalDefaultCipherSuites returns an appropriate cipher
179
// suite to use depending on the hardware support for AES.
180
func optimalDefaultCipherSuites() []uint16 {
181
if cpuid.CPU.Supports(cpuid.AESNI) {
182
return defaultCipherSuitesWithAESNI
183
}
184
return defaultCipherSuitesWithoutAESNI
185
}
186
187
var tlsHandshakeErrorPrefix = []byte("http: TLS handshake error")
188
189
type logrusErrorWriter struct{}
190
191
func (w logrusErrorWriter) Write(p []byte) (int, error) {
192
if bytes.Contains(p, tlsHandshakeErrorPrefix) {
193
return len(p), nil
194
}
195
196
log.Errorf("%s", string(p))
197
return len(p), nil
198
}
199
200