@@ -6,7 +6,6 @@ package sshproxy
6
6
7
7
import (
8
8
"context"
9
- "errors"
10
9
"fmt"
11
10
"net"
12
11
"strings"
@@ -17,16 +16,52 @@ import (
17
16
supervisor "github.com/gitpod-io/gitpod/supervisor/api"
18
17
tracker "github.com/gitpod-io/gitpod/ws-proxy/pkg/analytics"
19
18
p "github.com/gitpod-io/gitpod/ws-proxy/pkg/proxy"
19
+ "github.com/prometheus/client_golang/prometheus"
20
20
"golang.org/x/crypto/ssh"
21
21
"golang.org/x/xerrors"
22
22
"google.golang.org/grpc"
23
+ "sigs.k8s.io/controller-runtime/pkg/metrics"
23
24
)
24
25
25
26
const GitpodUsername = "gitpod"
26
27
27
- var ErrWorkspaceNotFound = errors .New ("not found workspace" )
28
- var ErrAuthFailed = errors .New ("auth failed" )
29
- var ErrUsernameFormat = errors .New ("username format is not correct" )
28
+ var (
29
+ SSHConnectionCount = prometheus .NewGauge (prometheus.GaugeOpts {
30
+ Name : "gitpod_ws_proxy_ssh_connection_count" ,
31
+ Help : "Current number of SSH connection" ,
32
+ })
33
+
34
+ SSHAttemptTotal = prometheus .NewCounterVec (prometheus.CounterOpts {
35
+ Name : "gitpod_ws_proxy_ssh_attempt_total" ,
36
+ Help : "Total number of SSH attempt" ,
37
+ }, []string {"status" , "error_type" })
38
+ )
39
+
40
+ var (
41
+ ErrWorkspaceNotFound = NewSSHError ("WS_NOTFOUND" , "not found workspace" )
42
+ ErrAuthFailed = NewSSHError ("AUTH_FAILED" , "auth failed" )
43
+ ErrUsernameFormat = NewSSHError ("USER_FORMAT" , "username format is not correct" )
44
+ ErrMissPrivateKey = NewSSHError ("MISS_KEY" , "missing privateKey" )
45
+ ErrConnFailed = NewSSHError ("CONN_FAILED" , "cannot to connect with workspace" )
46
+ ErrCreateSSHKey = NewSSHError ("CREATE_KEY_FAILED" , "cannot create private pair in workspace" )
47
+ )
48
+
49
+ type SSHError struct {
50
+ shortName string
51
+ description string
52
+ }
53
+
54
+ func (e SSHError ) Error () string {
55
+ return e .description
56
+ }
57
+
58
+ func (e SSHError ) ShortName () string {
59
+ return e .shortName
60
+ }
61
+
62
+ func NewSSHError (shortName string , description string ) SSHError {
63
+ return SSHError {shortName : shortName , description : description }
64
+ }
30
65
31
66
type Session struct {
32
67
Conn * ssh.ServerConn
@@ -45,6 +80,13 @@ type Server struct {
45
80
workspaceInfoProvider p.WorkspaceInfoProvider
46
81
}
47
82
83
+ func init () {
84
+ metrics .Registry .MustRegister (
85
+ SSHConnectionCount ,
86
+ SSHAttemptTotal ,
87
+ )
88
+ }
89
+
48
90
// New creates a new SSH proxy server
49
91
50
92
func New (signers []ssh.Signer , workspaceInfoProvider p.WorkspaceInfoProvider , heartbeat Heartbeat ) * Server {
@@ -75,7 +117,7 @@ func New(signers []ssh.Signer, workspaceInfoProvider p.WorkspaceInfoProvider, he
75
117
workspaceId , ownerToken = args [0 ], args [1 ]
76
118
wsInfo , err = server .Authenticator (workspaceId , ownerToken )
77
119
if err == nil {
78
- err = errors . New ( "miss private key" )
120
+ err = ErrMissPrivateKey
79
121
}
80
122
return
81
123
}
@@ -112,10 +154,25 @@ func New(signers []ssh.Signer, workspaceInfoProvider p.WorkspaceInfoProvider, he
112
154
return server
113
155
}
114
156
157
+ func ReportSSHAttemptMetrics (err error ) {
158
+ if err == nil {
159
+ SSHAttemptTotal .WithLabelValues ("success" ).Inc ()
160
+ return
161
+ }
162
+ errorType := "OTHERS"
163
+ if serverAuthErr , ok := err .(* ssh.ServerAuthError ); ok && len (serverAuthErr .Errors ) > 0 {
164
+ if authErr , ok := serverAuthErr .Errors [len (serverAuthErr .Errors )- 1 ].(SSHError ); ok {
165
+ errorType = authErr .ShortName ()
166
+ }
167
+ }
168
+ SSHAttemptTotal .WithLabelValues ("failed" , errorType ).Inc ()
169
+ }
170
+
115
171
func (s * Server ) HandleConn (c net.Conn ) {
116
172
sshConn , chans , reqs , err := ssh .NewServerConn (c , s .sshConfig )
117
173
if err != nil {
118
174
c .Close ()
175
+ ReportSSHAttemptMetrics (err )
119
176
return
120
177
}
121
178
defer sshConn .Close ()
@@ -127,13 +184,16 @@ func (s *Server) HandleConn(c net.Conn) {
127
184
workspaceId := sshConn .Permissions .Extensions ["workspaceId" ]
128
185
wsInfo := s .workspaceInfoProvider .WorkspaceInfo (workspaceId )
129
186
if wsInfo == nil {
187
+ ReportSSHAttemptMetrics (ErrWorkspaceNotFound )
130
188
return
131
189
}
132
190
ctx , cancel := context .WithTimeout (context .Background (), time .Second * 5 )
133
191
key , err := s .GetWorkspaceSSHKey (ctx , wsInfo .IPAddress )
134
192
if err != nil {
135
193
cancel ()
136
- s .TrackSSHConnection (wsInfo , "connect" , err )
194
+ s .TrackSSHConnection (wsInfo , "connect" , ErrCreateSSHKey )
195
+ ReportSSHAttemptMetrics (ErrCreateSSHKey )
196
+ log .WithField ("instanceId" , wsInfo .InstanceID ).WithError (err ).Error ("failed to create private pair in workspace" )
137
197
return
138
198
}
139
199
cancel ()
@@ -147,7 +207,8 @@ func (s *Server) HandleConn(c net.Conn) {
147
207
remoteAddr := wsInfo .IPAddress + ":23001"
148
208
conn , err := net .Dial ("tcp" , remoteAddr )
149
209
if err != nil {
150
- s .TrackSSHConnection (wsInfo , "connect" , err )
210
+ s .TrackSSHConnection (wsInfo , "connect" , ErrConnFailed )
211
+ ReportSSHAttemptMetrics (ErrConnFailed )
151
212
log .WithField ("instanceId" , wsInfo .InstanceID ).WithField ("workspaceIP" , wsInfo .IPAddress ).WithError (err ).Error ("dail failed" )
152
213
return
153
214
}
@@ -164,7 +225,8 @@ func (s *Server) HandleConn(c net.Conn) {
164
225
Timeout : 10 * time .Second ,
165
226
})
166
227
if err != nil {
167
- s .TrackSSHConnection (wsInfo , "connect" , err )
228
+ s .TrackSSHConnection (wsInfo , "connect" , ErrConnFailed )
229
+ ReportSSHAttemptMetrics (ErrConnFailed )
168
230
log .WithField ("instanceId" , wsInfo .InstanceID ).WithField ("workspaceIP" , wsInfo .IPAddress ).WithError (err ).Error ("connect failed" )
169
231
return
170
232
}
@@ -173,10 +235,13 @@ func (s *Server) HandleConn(c net.Conn) {
173
235
ctx , cancel = context .WithCancel (context .Background ())
174
236
175
237
s .TrackSSHConnection (wsInfo , "connect" , nil )
238
+ SSHConnectionCount .Inc ()
239
+ ReportSSHAttemptMetrics (nil )
176
240
177
241
go func () {
178
242
client .Wait ()
179
243
cancel ()
244
+ defer SSHConnectionCount .Dec ()
180
245
}()
181
246
182
247
for newChannel := range chans {
0 commit comments