github.com/secure-build/gitlab-runner@v12.5.0+incompatible/session/session.go (about) 1 package session 2 3 import ( 4 "net/http" 5 "reflect" 6 "sync" 7 8 "github.com/gorilla/mux" 9 "github.com/gorilla/websocket" 10 "github.com/sirupsen/logrus" 11 12 "gitlab.com/gitlab-org/gitlab-runner/helpers" 13 "gitlab.com/gitlab-org/gitlab-runner/session/proxy" 14 "gitlab.com/gitlab-org/gitlab-runner/session/terminal" 15 ) 16 17 type connectionInUseError struct{} 18 19 func (connectionInUseError) Error() string { 20 return "Connection already in use" 21 } 22 23 type Session struct { 24 Endpoint string 25 Token string 26 27 mux *mux.Router 28 29 interactiveTerminal terminal.InteractiveTerminal 30 terminalConn terminal.Conn 31 32 proxyPool proxy.Pool 33 34 // Signal when client disconnects from terminal. 35 DisconnectCh chan error 36 // Signal when terminal session timeout. 37 TimeoutCh chan error 38 39 log *logrus.Entry 40 sync.Mutex 41 } 42 43 func NewSession(logger *logrus.Entry) (*Session, error) { 44 endpoint, token, err := generateEndpoint() 45 if err != nil { 46 return nil, err 47 } 48 49 if logger == nil { 50 logger = logrus.NewEntry(logrus.StandardLogger()) 51 } 52 53 logger = logger.WithField("uri", endpoint) 54 55 sess := &Session{ 56 Endpoint: endpoint, 57 Token: token, 58 DisconnectCh: make(chan error), 59 TimeoutCh: make(chan error), 60 61 log: logger, 62 } 63 64 sess.setMux() 65 66 return sess, nil 67 } 68 69 func generateEndpoint() (string, string, error) { 70 sessionUUID, err := helpers.GenerateRandomUUID(32) 71 if err != nil { 72 return "", "", err 73 } 74 75 token, err := generateToken() 76 if err != nil { 77 return "", "", err 78 } 79 80 return "/session/" + sessionUUID, token, nil 81 } 82 83 func generateToken() (string, error) { 84 token, err := helpers.GenerateRandomUUID(32) 85 if err != nil { 86 return "", err 87 } 88 89 return token, nil 90 } 91 92 func (s *Session) withAuthorization(next http.Handler) http.Handler { 93 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 94 logger := s.log.WithField("uri", r.RequestURI) 95 logger.Debug("Endpoint session request") 96 97 if s.Token != r.Header.Get("Authorization") { 98 logger.Error("Authorization header is not valid") 99 http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 100 return 101 } 102 103 next.ServeHTTP(w, r) 104 }) 105 } 106 107 func (s *Session) setMux() { 108 s.Lock() 109 defer s.Unlock() 110 111 s.mux = mux.NewRouter() 112 s.mux.Handle(s.Endpoint+"/proxy/{resource}/{port}/{requestedUri:.*}", s.withAuthorization(http.HandlerFunc(s.proxyHandler))) 113 s.mux.Handle(s.Endpoint+"/exec", s.withAuthorization(http.HandlerFunc(s.execHandler))) 114 } 115 116 func (s *Session) proxyHandler(w http.ResponseWriter, r *http.Request) { 117 logger := s.log.WithField("uri", r.RequestURI) 118 logger.Debug("Proxy session request") 119 120 params := mux.Vars(r) 121 serviceName := params["resource"] 122 123 serviceProxy := s.proxyPool[serviceName] 124 if serviceProxy == nil { 125 logger.Warn("Proxy not found") 126 http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 127 return 128 } 129 130 if serviceProxy.ConnectionHandler == nil { 131 logger.Warn("Proxy connection handler is not defined") 132 http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 133 return 134 } 135 136 serviceProxy.ConnectionHandler.ProxyRequest(w, r, params["requestedUri"], params["port"], serviceProxy.Settings) 137 } 138 139 func (s *Session) execHandler(w http.ResponseWriter, r *http.Request) { 140 logger := s.log.WithField("uri", r.RequestURI) 141 logger.Debug("Exec terminal session request") 142 143 if !s.terminalAvailable() { 144 logger.Error("Interactive terminal not set") 145 http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) 146 return 147 } 148 149 if !websocket.IsWebSocketUpgrade(r) { 150 logger.Error("Request is not a web socket connection") 151 http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) 152 return 153 } 154 155 terminalConn, err := s.newTerminalConn() 156 if _, ok := err.(connectionInUseError); ok { 157 logger.Warn("Terminal already connected, revoking connection") 158 http.Error(w, http.StatusText(http.StatusLocked), http.StatusLocked) 159 return 160 } 161 162 if err != nil { 163 logger.WithError(err).Error("Failed to connect to terminal") 164 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 165 return 166 } 167 168 defer s.closeTerminalConn(terminalConn) 169 logger.Debugln("Starting terminal session") 170 terminalConn.Start(w, r, s.TimeoutCh, s.DisconnectCh) 171 } 172 173 func (s *Session) terminalAvailable() bool { 174 s.Lock() 175 defer s.Unlock() 176 177 return s.interactiveTerminal != nil 178 } 179 180 func (s *Session) newTerminalConn() (terminal.Conn, error) { 181 s.Lock() 182 defer s.Unlock() 183 184 if s.terminalConn != nil { 185 return nil, connectionInUseError{} 186 } 187 188 conn, err := s.interactiveTerminal.Connect() 189 if err != nil { 190 return nil, err 191 } 192 193 s.terminalConn = conn 194 195 return conn, nil 196 } 197 198 func (s *Session) closeTerminalConn(conn terminal.Conn) { 199 s.Lock() 200 defer s.Unlock() 201 202 err := conn.Close() 203 if err != nil { 204 s.log.WithError(err).Warn("Failed to close terminal connection") 205 } 206 207 if reflect.ValueOf(s.terminalConn) == reflect.ValueOf(conn) { 208 s.log.Warningln("Closed active terminal connection") 209 s.terminalConn = nil 210 } 211 } 212 213 func (s *Session) SetInteractiveTerminal(interactiveTerminal terminal.InteractiveTerminal) { 214 s.Lock() 215 defer s.Unlock() 216 s.interactiveTerminal = interactiveTerminal 217 } 218 219 func (s *Session) SetProxyPool(pooler proxy.Pooler) { 220 s.Lock() 221 defer s.Unlock() 222 s.proxyPool = pooler.Pool() 223 } 224 225 func (s *Session) Mux() *mux.Router { 226 return s.mux 227 } 228 229 func (s *Session) Connected() bool { 230 s.Lock() 231 defer s.Unlock() 232 233 return s.terminalConn != nil 234 } 235 236 func (s *Session) Kill() error { 237 s.Lock() 238 defer s.Unlock() 239 240 if s.terminalConn == nil { 241 return nil 242 } 243 244 err := s.terminalConn.Close() 245 s.terminalConn = nil 246 247 return err 248 }