forked from gravitational/teleport
/
teleterm.go
162 lines (139 loc) · 4.83 KB
/
teleterm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package teleterm
import (
"context"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"github.com/gravitational/trace"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/gravitational/teleport/lib/teleterm/apiserver"
"github.com/gravitational/teleport/lib/teleterm/clusters"
"github.com/gravitational/teleport/lib/teleterm/daemon"
)
// Serve starts daemon service
func Serve(ctx context.Context, cfg Config) error {
if err := cfg.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
grpcCredentials, err := createGRPCCredentials(cfg.Addr, cfg.CertsDir)
if err != nil {
return trace.Wrap(err)
}
storage, err := clusters.NewStorage(clusters.Config{
Dir: cfg.HomeDir,
InsecureSkipVerify: cfg.InsecureSkipVerify,
})
if err != nil {
return trace.Wrap(err)
}
daemonService, err := daemon.New(daemon.Config{
Storage: storage,
CreateTshdEventsClientCredsFunc: grpcCredentials.tshdEvents,
PrehogAddr: cfg.PrehogAddr,
KubeconfigsDir: cfg.KubeconfigsDir,
AgentsDir: cfg.AgentsDir,
})
if err != nil {
return trace.Wrap(err)
}
apiServer, err := apiserver.New(apiserver.Config{
HostAddr: cfg.Addr,
Daemon: daemonService,
TshdServerCreds: grpcCredentials.tshd,
ListeningC: cfg.ListeningC,
})
if err != nil {
return trace.Wrap(err)
}
serverAPIWait := make(chan error)
go func() {
err := apiServer.Serve()
serverAPIWait <- err
}()
// Wait for shutdown signals
go func() {
shutdownSignals := []os.Signal{os.Interrupt, syscall.SIGTERM}
c := make(chan os.Signal, len(shutdownSignals))
signal.Notify(c, shutdownSignals...)
select {
case <-ctx.Done():
log.Info("Context closed, stopping service.")
case sig := <-c:
log.Infof("Captured %s, stopping service.", sig)
}
daemonService.Stop()
apiServer.Stop()
}()
errAPI := <-serverAPIWait
if errAPI != nil {
return trace.Wrap(errAPI, "shutting down due to API Server error")
}
return nil
}
type grpcCredentials struct {
tshd grpc.ServerOption
tshdEvents daemon.CreateTshdEventsClientCredsFunc
}
func createGRPCCredentials(tshdServerAddress string, certsDir string) (*grpcCredentials, error) {
shouldUseMTLS := strings.HasPrefix(tshdServerAddress, "tcp://")
if !shouldUseMTLS {
return &grpcCredentials{
tshd: grpc.Creds(nil),
tshdEvents: func() (grpc.DialOption, error) {
return grpc.WithTransportCredentials(insecure.NewCredentials()), nil
},
}, nil
}
rendererCertPath := filepath.Join(certsDir, rendererCertFileName)
mainProcessCertPath := filepath.Join(certsDir, mainProcessCertFileName)
tshdCertPath := filepath.Join(certsDir, tshdCertFileName)
tshdKeyPair, err := generateAndSaveCert(tshdCertPath)
if err != nil {
return nil, trace.Wrap(err)
}
tshdCreds, err := createServerCredentials(
tshdKeyPair,
// Client certs will be read on an incoming connection. The client setup in the Electron app is
// orchestrated in a way where the client saves its cert to disk before initiating a connection.
[]string{rendererCertPath, mainProcessCertPath},
)
if err != nil {
return nil, trace.Wrap(err)
}
// To create client creds for tshd events service, we need to read the server cert. However, at
// this point we'd need to wait for the Electron app to save the cert under rendererCertPath.
//
// Instead of waiting for it, we're going to capture the logic in a function that's going to be
// called after the Electron app calls UpdateTshdEventsServerAddress of the Terminal service.
// Since this calls the gRPC server hosted by tsh, we can assume that by this point the Electron
// app has saved the cert to disk – without the cert, it wouldn't be able to call the tsh server.
createTshdEventsClientCredsFunc := func() (grpc.DialOption, error) {
creds, err := createClientCredentials(tshdKeyPair, rendererCertPath)
return creds, trace.Wrap(err, "could not create tshd events client credentials")
}
return &grpcCredentials{
tshd: tshdCreds,
tshdEvents: createTshdEventsClientCredsFunc,
}, nil
}