Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'workhorse/doc/architecture/channel.md')
-rw-r--r--workhorse/doc/architecture/channel.md194
1 files changed, 194 insertions, 0 deletions
diff --git a/workhorse/doc/architecture/channel.md b/workhorse/doc/architecture/channel.md
new file mode 100644
index 00000000000..8423405a9cf
--- /dev/null
+++ b/workhorse/doc/architecture/channel.md
@@ -0,0 +1,194 @@
+# Websocket channel support
+
+In some cases, GitLab can provide in-browser terminal access to an
+environment (which is a running server or container, onto which a
+project has been deployed), or even access to services running in CI
+through a WebSocket. Workhorse manages the WebSocket upgrade and
+long-lived connection to the websocket connection, which frees
+up GitLab to process other requests.
+
+This document outlines the architecture of these connections.
+
+## Introduction to WebSockets
+
+A websocket is an "upgraded" HTTP/1.1 request. Their purpose is to
+permit bidirectional communication between a client and a server.
+**Websockets are not HTTP**. Clients can send messages (known as
+frames) to the server at any time, and vice-versa. Client messages
+are not necessarily requests, and server messages are not necessarily
+responses. WebSocket URLs have schemes like `ws://` (unencrypted) or
+`wss://` (TLS-secured).
+
+When requesting an upgrade to WebSocket, the browser sends a HTTP/1.1
+request that looks like this:
+
+```
+GET /path.ws HTTP/1.1
+Connection: upgrade
+Upgrade: websocket
+Sec-WebSocket-Protocol: terminal.gitlab.com
+# More headers, including security measures
+```
+
+At this point, the connection is still HTTP, so this is a request and
+the server can send a normal HTTP response, including `404 Not Found`,
+`500 Internal Server Error`, etc.
+
+If the server decides to permit the upgrade, it will send a HTTP
+`101 Switching Protocols` response. From this point, the connection
+is no longer HTTP. It is a WebSocket and frames, not HTTP requests,
+will flow over it. The connection will persist until the client or
+server closes the connection.
+
+In addition to the subprotocol, individual websocket frames may
+also specify a message type - examples include `BinaryMessage`,
+`TextMessage`, `Ping`, `Pong` or `Close`. Only binary frames can
+contain arbitrary data - other frames are expected to be valid
+UTF-8 strings, in addition to any subprotocol expectations.
+
+## Browser to Workhorse
+
+Using the terminal as an example, GitLab serves a JavaScript terminal
+emulator to the browser on a URL like
+`https://gitlab.com/group/project/-/environments/1/terminal`.
+This opens a websocket connection to, e.g.,
+`wss://gitlab.com/group/project/-/environments/1/terminal.ws`,
+This endpoint doesn't exist in GitLab - only in Workhorse.
+
+When receiving the connection, Workhorse first checks that the
+client is authorized to access the requested terminal. It does
+this by performing a "preauthentication" request to GitLab.
+
+If the client has the appropriate permissions and the terminal
+exists, GitLab responds with a successful response that includes
+details of the terminal that the client should be connected to.
+Otherwise, it returns an appropriate HTTP error response.
+
+Errors are passed back to the client as HTTP responses, but if
+GitLab returns valid terminal details to Workhorse, it will
+connect to the specified terminal, upgrade the browser to a
+WebSocket, and proxy between the two connections for as long
+as the browser's credentials are valid. Workhorse will also
+send regular `PingMessage` control frames to the browser, to
+keep intervening proxies from terminating the connection
+while the browser is present.
+
+The browser must request an upgrade with a specific subprotocol:
+
+### `terminal.gitlab.com`
+
+This subprotocol considers `TextMessage` frames to be invalid.
+Control frames, such as `PingMessage` or `CloseMessage`, have
+their usual meanings.
+
+`BinaryMessage` frames sent from the browser to the server are
+arbitrary text input.
+
+`BinaryMessage` frames sent from the server to the browser are
+arbitrary text output.
+
+These frames are expected to contain ANSI text control codes
+and may be in any encoding.
+
+### `base64.terminal.gitlab.com`
+
+This subprotocol considers `BinaryMessage` frames to be invalid.
+Control frames, such as `PingMessage` or `CloseMessage`, have
+their usual meanings.
+
+`TextMessage` frames sent from the browser to the server are
+base64-encoded arbitrary text input (so the server must
+base64-decode them before inputting them).
+
+`TextMessage` frames sent from the server to the browser are
+base64-encoded arbitrary text output (so the browser must
+base64-decode them before outputting them).
+
+In their base64-encoded form, these frames are expected to
+contain ANSI terminal control codes, and may be in any encoding.
+
+## Workhorse to GitLab
+
+Using again the terminal as an example, before upgrading the browser,
+Workhorse sends a normal HTTP request to GitLab on a URL like
+`https://gitlab.com/group/project/environments/1/terminal.ws/authorize`.
+This returns a JSON response containing details of where the
+terminal can be found, and how to connect it. In particular,
+the following details are returned in case of success:
+
+* WebSocket URL to **connect** to, e.g.: `wss://example.com/terminals/1.ws?tty=1`
+* WebSocket subprotocols to support, e.g.: `["channel.k8s.io"]`
+* Headers to send, e.g.: `Authorization: Token xxyyz..`
+* Certificate authority to verify `wss` connections with (optional)
+
+Workhorse periodically re-checks this endpoint, and if it gets an
+error response, or the details of the terminal change, it will
+terminate the websocket session.
+
+## Workhorse to the WebSocket server
+
+In GitLab, environments or CI jobs may have a deployment service (e.g.,
+`KubernetesService`) associated with them. This service knows
+where the terminals or the service for an environment may be found, and these
+details are returned to Workhorse by GitLab.
+
+These URLs are *also* WebSocket URLs, and GitLab tells Workhorse
+which subprotocols to speak over the connection, along with any
+authentication details required by the remote end.
+
+Before upgrading the browser's connection to a websocket,
+Workhorse opens a HTTP client connection, according to the
+details given to it by Workhorse, and attempts to upgrade
+that connection to a websocket. If it fails, an error
+response is sent to the browser; otherwise, the browser is
+also upgraded.
+
+Workhorse now has two websocket connections, albeit with
+differing subprotocols. It decodes incoming frames from the
+browser, re-encodes them to the the channel's subprotocol, and
+sends them to the channel. Similarly, it decodes incoming
+frames from the channel, re-encodes them to the browser's
+subprotocol, and sends them to the browser.
+
+When either connection closes or enters an error state,
+Workhorse detects the error and closes the other connection,
+terminating the channel session. If the browser is the
+connection that has disconnected, Workhorse will send an ANSI
+`End of Transmission` control code (the `0x04` byte) to the
+channel, encoded according to the appropriate subprotocol.
+Workhorse will automatically reply to any websocket ping frame
+sent by the channel, to avoid being disconnected.
+
+Currently, Workhorse only supports the following subprotocols.
+Supporting new deployment services will require new subprotocols
+to be supported:
+
+### `channel.k8s.io`
+
+Used by Kubernetes, this subprotocol defines a simple multiplexed
+channel.
+
+Control frames have their usual meanings. `TextMessage` frames are
+invalid. `BinaryMessage` frames represent I/O to a specific file
+descriptor.
+
+The first byte of each `BinaryMessage` frame represents the file
+descriptor (fd) number, as a `uint8` (so the value `0x00` corresponds
+to fd 0, `STDIN`, while `0x01` corresponds to fd 1, `STDOUT`).
+
+The remaining bytes represent arbitrary data. For frames received
+from the server, they are bytes that have been received from that
+fd. For frames sent to the server, they are bytes that should be
+written to that fd.
+
+### `base64.channel.k8s.io`
+
+Also used by Kubernetes, this subprotocol defines a similar multiplexed
+channel to `channel.k8s.io`. The main differences are:
+
+* `TextMessage` frames are valid, rather than `BinaryMessage` frames.
+* The first byte of each `TextMessage` frame represents the file
+ descriptor as a numeric UTF-8 character, so the character `U+0030`,
+ or "0", is fd 0, STDIN).
+* The remaining bytes represent base64-encoded arbitrary data.
+