Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-proxy/cmd/run.go
2498 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 cmd
6
7
import (
8
"context"
9
"net"
10
"net/http"
11
"os"
12
"path/filepath"
13
"time"
14
15
"github.com/bombsimon/logrusr/v2"
16
"github.com/spf13/cobra"
17
"google.golang.org/grpc"
18
"google.golang.org/grpc/credentials"
19
"google.golang.org/grpc/credentials/insecure"
20
corev1 "k8s.io/api/core/v1"
21
"k8s.io/apimachinery/pkg/api/errors"
22
"k8s.io/apimachinery/pkg/runtime"
23
"k8s.io/apimachinery/pkg/types"
24
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
25
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
26
ctrl "sigs.k8s.io/controller-runtime"
27
"sigs.k8s.io/controller-runtime/pkg/cache"
28
runtime_client "sigs.k8s.io/controller-runtime/pkg/client"
29
"sigs.k8s.io/controller-runtime/pkg/healthz"
30
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
31
32
common_grpc "github.com/gitpod-io/gitpod/common-go/grpc"
33
"github.com/gitpod-io/gitpod/common-go/log"
34
"github.com/gitpod-io/gitpod/common-go/pprof"
35
wsmanapi "github.com/gitpod-io/gitpod/ws-manager/api"
36
workspacev1 "github.com/gitpod-io/gitpod/ws-manager/api/crd/v1"
37
"github.com/gitpod-io/gitpod/ws-proxy/pkg/config"
38
"github.com/gitpod-io/gitpod/ws-proxy/pkg/proxy"
39
"github.com/gitpod-io/gitpod/ws-proxy/pkg/sshproxy"
40
"github.com/gitpod-io/golang-crypto/ssh"
41
)
42
43
var (
44
jsonLog bool
45
verbose bool
46
)
47
48
// runCmd represents the run command.
49
var runCmd = &cobra.Command{
50
Use: "run <config.json>",
51
Short: "Starts ws-proxy",
52
Args: cobra.ExactArgs(1),
53
Run: func(cmd *cobra.Command, args []string) {
54
cfg, err := config.GetConfig(args[0])
55
if err != nil {
56
log.WithError(err).WithField("filename", args[0]).Fatal("cannot load config")
57
}
58
59
ctrl.SetLogger(logrusr.New(log.Log))
60
61
opts := ctrl.Options{
62
Scheme: scheme,
63
HealthProbeBindAddress: cfg.ReadinessProbeAddr,
64
LeaderElection: false,
65
Cache: cache.Options{
66
DefaultNamespaces: map[string]cache.Config{
67
cfg.Namespace: {},
68
},
69
},
70
}
71
72
if cfg.PrometheusAddr != "" {
73
opts.Metrics = metricsserver.Options{BindAddress: cfg.PrometheusAddr}
74
}
75
76
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), opts)
77
if err != nil {
78
log.WithError(err).Fatal(err, "unable to start manager")
79
}
80
81
infoprov, err := proxy.NewCRDWorkspaceInfoProvider(mgr.GetClient(), mgr.GetScheme())
82
if err != nil {
83
log.WithError(err).Fatal("cannot create CRD-based info provider")
84
}
85
if err = infoprov.SetupWithManager(mgr); err != nil {
86
log.WithError(err).Fatal(err, "unable to create CRD-based info provider", "controller", "Workspace")
87
}
88
89
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
90
log.WithError(err).Fatal("unable to set up health check")
91
}
92
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
93
log.WithError(err).Fatal(err, "unable to set up ready check")
94
}
95
if err := mgr.AddReadyzCheck("readyz", readyCheck(mgr.GetClient(), cfg.Namespace)); err != nil {
96
log.WithError(err).Fatal(err, "unable to set up ready check")
97
}
98
99
if cfg.PProfAddr != "" {
100
go pprof.Serve(cfg.PProfAddr)
101
}
102
103
log.Infof("workspace info provider started")
104
105
var heartbeat sshproxy.Heartbeat
106
if wsm := cfg.WorkspaceManager; wsm != nil {
107
var dialOption grpc.DialOption = grpc.WithTransportCredentials(insecure.NewCredentials())
108
if wsm.TLS.CA != "" && wsm.TLS.Cert != "" && wsm.TLS.Key != "" {
109
tlsConfig, err := common_grpc.ClientAuthTLSConfig(
110
wsm.TLS.CA, wsm.TLS.Cert, wsm.TLS.Key,
111
common_grpc.WithSetRootCAs(true),
112
common_grpc.WithServerName("ws-manager"),
113
)
114
if err != nil {
115
log.WithField("config", wsm.TLS).Error("Cannot load ws-manager certs - this is a configuration issue.")
116
log.WithError(err).Fatal("cannot load ws-manager certs")
117
}
118
119
dialOption = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))
120
}
121
122
grpcOpts := common_grpc.DefaultClientOptions()
123
grpcOpts = append(grpcOpts, dialOption)
124
125
log.Info("Attempting to dial ws-manager, it's a blocking call that retries...")
126
conn, err := grpc.Dial(wsm.Addr, grpcOpts...)
127
// you will never get here if ws-manager is crashing, instead the readiness check for ws-proxy will restart the pod
128
if err != nil {
129
log.WithError(err).Fatal("cannot connect to ws-manager")
130
}
131
132
heartbeat = &sshproxy.WorkspaceManagerHeartbeat{
133
Client: wsmanapi.NewWorkspaceManagerClient(conn),
134
}
135
}
136
137
// SSH Gateway
138
139
var caKey ssh.Signer
140
readCAKeyFile := func() {
141
caPrivateKeyB, err := os.ReadFile(cfg.Proxy.SSHGatewayCAKeyFile)
142
if err != nil {
143
log.WithError(err).Error("cannot read SSH Gateway CA key")
144
return
145
}
146
c, err := ssh.ParsePrivateKey(caPrivateKeyB)
147
if err != nil {
148
log.WithError(err).Error("cannot parse SSH Gateway CA key")
149
return
150
}
151
caKey = c
152
}
153
154
if cfg.Proxy.SSHGatewayCAKeyFile != "" {
155
readCAKeyFile()
156
}
157
158
var signers []ssh.Signer
159
var sshGatewayServer *sshproxy.Server
160
flist, err := os.ReadDir("/mnt/host-key")
161
if err == nil && len(flist) > 0 {
162
for _, f := range flist {
163
if f.IsDir() {
164
continue
165
}
166
b, err := os.ReadFile(filepath.Join("/mnt/host-key", f.Name()))
167
if err != nil {
168
continue
169
}
170
hostSigner, err := ssh.ParsePrivateKey(b)
171
if err != nil {
172
continue
173
}
174
signers = append(signers, hostSigner)
175
}
176
if len(signers) > 0 {
177
sshGatewayServer = sshproxy.New(signers, infoprov, heartbeat, caKey)
178
l, err := net.Listen("tcp", ":2200")
179
if err != nil {
180
panic(err)
181
}
182
go sshGatewayServer.Serve(l)
183
log.Info("SSHGateway is up and running")
184
}
185
}
186
187
ctrlCtx := ctrl.SetupSignalHandler()
188
189
go func() {
190
log.Infof("startint proxying on %s", cfg.Ingress.HTTPAddress)
191
proxy.NewWorkspaceProxy(cfg.Ingress, cfg.Proxy, proxy.HostBasedRouter(cfg.Ingress.Header, cfg.Proxy.GitpodInstallation.WorkspaceHostSuffix, cfg.Proxy.GitpodInstallation.WorkspaceHostSuffixRegex), infoprov, sshGatewayServer).MustServe(ctrlCtx)
192
}()
193
194
log.Info("🚪 ws-proxy is up and running")
195
if err := mgr.Start(ctrlCtx); err != nil {
196
log.WithError(err).Fatal(err, "problem starting ws-proxy")
197
}
198
199
log.Info("Received SIGINT - shutting down")
200
},
201
}
202
203
func init() {
204
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
205
utilruntime.Must(workspacev1.AddToScheme(scheme))
206
rootCmd.AddCommand(runCmd)
207
}
208
209
var scheme = runtime.NewScheme()
210
211
// Ready check that verify we can list pods
212
func readyCheck(client runtime_client.Client, namespace string) func(*http.Request) error {
213
return func(*http.Request) error {
214
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
215
defer cancel()
216
217
var wsProxyPod corev1.Pod
218
err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: "readyz-pod"}, &wsProxyPod)
219
if errors.IsNotFound(err) {
220
// readyz-pod is not a valid name
221
// we just need to check there are no errors reaching the API server
222
return nil
223
}
224
225
return err
226
}
227
}
228
229