diff options
Diffstat (limited to 'workhorse/internal/channel/channel.go')
-rw-r--r-- | workhorse/internal/channel/channel.go | 132 |
1 files changed, 132 insertions, 0 deletions
diff --git a/workhorse/internal/channel/channel.go b/workhorse/internal/channel/channel.go new file mode 100644 index 00000000000..381ce95df82 --- /dev/null +++ b/workhorse/internal/channel/channel.go @@ -0,0 +1,132 @@ +package channel + +import ( + "fmt" + "net/http" + "time" + + "github.com/gorilla/websocket" + + "gitlab.com/gitlab-org/labkit/log" + + "gitlab.com/gitlab-org/gitlab-workhorse/internal/api" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" +) + +var ( + // See doc/channel.md for documentation of this subprotocol + subprotocols = []string{"terminal.gitlab.com", "base64.terminal.gitlab.com"} + upgrader = &websocket.Upgrader{Subprotocols: subprotocols} + ReauthenticationInterval = 5 * time.Minute + BrowserPingInterval = 30 * time.Second +) + +func Handler(myAPI *api.API) http.Handler { + return myAPI.PreAuthorizeHandler(func(w http.ResponseWriter, r *http.Request, a *api.Response) { + if err := a.Channel.Validate(); err != nil { + helper.Fail500(w, r, err) + return + } + + proxy := NewProxy(2) // two stoppers: auth checker, max time + checker := NewAuthChecker( + authCheckFunc(myAPI, r, "authorize"), + a.Channel, + proxy.StopCh, + ) + defer checker.Close() + go checker.Loop(ReauthenticationInterval) + go closeAfterMaxTime(proxy, a.Channel.MaxSessionTime) + + ProxyChannel(w, r, a.Channel, proxy) + }, "authorize") +} + +func ProxyChannel(w http.ResponseWriter, r *http.Request, settings *api.ChannelSettings, proxy *Proxy) { + server, err := connectToServer(settings, r) + if err != nil { + helper.Fail500(w, r, err) + log.ContextLogger(r.Context()).WithError(err).Print("Channel: connecting to server failed") + return + } + defer server.UnderlyingConn().Close() + serverAddr := server.UnderlyingConn().RemoteAddr().String() + + client, err := upgradeClient(w, r) + if err != nil { + log.ContextLogger(r.Context()).WithError(err).Print("Channel: upgrading client to websocket failed") + return + } + + // Regularly send ping messages to the browser to keep the websocket from + // being timed out by intervening proxies. + go pingLoop(client) + + defer client.UnderlyingConn().Close() + clientAddr := getClientAddr(r) // We can't know the port with confidence + + logEntry := log.WithContextFields(r.Context(), log.Fields{ + "clientAddr": clientAddr, + "serverAddr": serverAddr, + }) + + logEntry.Print("Channel: started proxying") + + defer logEntry.Print("Channel: finished proxying") + + if err := proxy.Serve(server, client, serverAddr, clientAddr); err != nil { + logEntry.WithError(err).Print("Channel: error proxying") + } +} + +// In the future, we might want to look at X-Client-Ip or X-Forwarded-For +func getClientAddr(r *http.Request) string { + return r.RemoteAddr +} + +func upgradeClient(w http.ResponseWriter, r *http.Request) (Connection, error) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return nil, err + } + + return Wrap(conn, conn.Subprotocol()), nil +} + +func pingLoop(conn Connection) { + for { + time.Sleep(BrowserPingInterval) + deadline := time.Now().Add(5 * time.Second) + if err := conn.WriteControl(websocket.PingMessage, nil, deadline); err != nil { + // Either the connection was already closed so no further pings are + // needed, or this connection is now dead and no further pings can + // be sent. + break + } + } +} + +func connectToServer(settings *api.ChannelSettings, r *http.Request) (Connection, error) { + settings = settings.Clone() + + helper.SetForwardedFor(&settings.Header, r) + + conn, _, err := settings.Dial() + if err != nil { + return nil, err + } + + return Wrap(conn, conn.Subprotocol()), nil +} + +func closeAfterMaxTime(proxy *Proxy, maxSessionTime int) { + if maxSessionTime == 0 { + return + } + + <-time.After(time.Duration(maxSessionTime) * time.Second) + proxy.StopCh <- fmt.Errorf( + "connection closed: session time greater than maximum time allowed - %v seconds", + maxSessionTime, + ) +} |