blob: 8f8ace3f08b2b7a6e815c7038f9a7dc32105b4dc [file] [log] [blame] [view]
# Secure Shell Relay Server Protocols
Some servers are accessible only behind a different secure server, and over a
web protocol like [XHR] or [WebSockets].
To support those use cases, Secure Shell supports connecting to machines through
relay servers.
Here we document the relay server protocol in case people want to create their
own relay servers for their own networks.
Google does not offer any public relay servers for you to access.
See the [FAQ] for details on open source relay server implementations.
This does not document the Secure Shell options it uses itself to connect
through relays.
See the [Options] document for those details.
[TOC]
## Corp Relay
This uses the id `corp-relay@google.com` (e.g. when using `--proxy-mode=`).
The main implementation for this can be found in [nassh_google_relay.js] with
supporting stream logic in [nassh_stream_google_relay.js].
### Protocol Overview
```
+--------+ Phase I +---------------+
| USER | ---(1 /cookie)---> | COOKIE SERVER |
+--------+ +---------------+
| | | |
| | | |
| | | Phase II
| | | |
| | | `---(3 /proxy)---> +--------------+
| | | | |
Phase III | | -----> +------------+
| | | | RELAY SERVER | | SSH SERVER |
| | `------(4 /read)------> | | <----- +------------+
| `--------(5 /write)-----> | |
`----------(6 /connect)---> +--------------+
```
You can think of this as a three phase process:
* (I) Client talks to a cookie server in order to authenticate the user, and
for the cookie server to tell the user which relay server to use.
* (II) Client talks to the relay server to establish a new session to the ssh
server (and allow the relay server to authenticate the user).
* (III) Client talks to the ssh server through the relay server using the
established session.
Across those phases are some major steps:
1. Connect to the cookie server using [/cookie].
This system may initiate a single-sign-on (SSO) flow if necessary, as well
as verify client certificates.
2. When the cookie server is done with authenticating the user, it responds
with details for which relay server to talk to in order to connect to the
actual ssh server.
Its response format is described below in [/cookie].
The relay host is expected to respond to requests for [/proxy] and either
[/connect] (for [WebSockets]) or [/read] & [/write] (for [XHR]).
3. Send a request to [/proxy] which establishes the ssh session with the remote
ssh server.
4. When using [XHR], use [/read] to read binary data from the ssh server via
the established relay server session. Every read requires a new connection
to be created.
5. When using [XHR], use [/write] to send binary data to the ssh server via the
established relay server session. Every write requires a new connection to
be created.
6. When using [WebSockets], use [/connect] to create a long lived bidirectional
socket for reading and writing binary data to the ssh sever via the
established relay server session.
### /cookie Protocol {#corp-relay-cookie}
Make a `GET` request to `PROTOCOL://COOKIE_HOST:COOKIE_PORT/cookie` to find the
relay server to talk to.
This allows the server to select from pools to help load balance internally.
`PROTOCOL` may be `http` or `https`. Secure Shell defaults to `http`.
`COOKIE_HOST` is the user-specified hostname for the cookie server.
This system could offer other services too which is why the port is not fixed.
`COOKIE_PORT` is the port on the server to connect to which defaults to 8022.
The [query string] settings:
* `ext=EXT_ID` (required): The Chrome extension id that the cookie server
should redirect to when it is finished.
* `path=PATH` (required): The path under the extension that the cookie server
should redirect to when it is finished.
* `version=VERSION`: The version of the cookie protocol. Only `2` is
supported. If `version=` is omitted, then the older version 1 is used
(note: `version=1` is not supported!).
* `method=METHOD`: The redirection method in the response (only used by
`version=2`. See the [method field section](#corp-relay-method) below for
more details.
It responds with an eventual redirect to `chrome://EXT_ID/PATH`.
The server might trigger intermediate redirects for its own purposes.
The exact redirect method might be [HTTP 302], or an [HTML meta refresh], or a
JavaScript redirect using a `<script>` tag.
The client only cares about the eventual redirect to `chrome://EXT_ID/PATH`.
The response format is determined by the `version` and `method` settings.
Version `1` will set the [URI fragment] (the part after the `#`) to
`USER@RELAY_HOST:RELAY_PORT` where each component is [URI encoded] as needed.
The `RELAY_PORT` part is optional.
The `USER` field is the username used to authenticate with the cookie server.
Secure Shell itself ignores this field though, so you can too.
Version `2` will send a JSON object; the response depends on the `METHOD`
specified earlier (see the [method field] for more details).
That object will have the fields:
* `endpoint` (string): The `RELAY_HOST:RELAY_PORT` (where `RELAY_PORT` is
optional).
* `error` (string): In case the cookie server rejects the request (e.g. bad
credentials), a human readable string can be placed here for display.
If `RELAY_PORT` is not specified, the default will be based on the protocol used
later when connecting to `RELAY_HOST`.
Typically `http` and `ws` use port 80 while `https` and `wss` use port 443.
It is up to the client to select the `RELAY_PROTOCOL` themselves.
#### method field {#corp-relay-method}
The `method` field is used with version `2` only.
It may be `direct` or `js-redirect`.
The `direct` method will return a JSON response with an [XSSI header].
The client is responsible for parsing that response and operating on it (e.g.
redirecting itself to `chrome://EXT_ID/PATH`).
The `js-redirect` method will generate a HTML document with a `<script>` tag to
redirect to the `chrome://EXT_ID/PATH` path.
The JSON response is [base64url] encoded in the [URI fragment].
Secure Shell only supports `js-redirect` currently.
### /proxy Protocol {#corp-relay-proxy}
This establishes a new session with the actual ssh server.
The `RELAY_HOST:RELAY_PORT` settings were passed back from the [/cookie]
connection made previously.
Make a `GET` request to `PROTOCOL://RELAY_HOST:RELAY_PORT/proxy` with the
[query string]:
* `host=HOST`: The ssh server to connect to. It may be an IP address or a
hostname, and does not need to be resolvable or accessible by the client.
* `port=PORT`: The port the ssh server is listening on.
This will return a session id (and optionally more [query string] fields) as
plain text.
The client should then paste this directly into the next phase -- either with
[/connect] (for [WebSockets]) or [/read] & [/write] (for [XHR]).
For example, it might be `4b2fbe8f4eff640b&host=foovpn-1.system.example.com`.
The session id allows the remote relay server to associate the connection with
an existing one, and the additional [query string] fields allow for things like
load distribution.
The extra fields are left unspecified to allow the relay server implementation
the freedom to pass through whatever they want.
### /connect Protocol (WebSocket) {#corp-relay-connect}
*** aside
If possible, you should use [WebSockets] as the protocol is much more efficient.
***
For [WebSockets], you need to only make one connection to `/connect` to the
`RELAY_PROTOCOL://RELAY_HOST:RELAY_PORT` host to create a bidirectional socket.
Use `arraybuffer` for the `binaryType` field.
The fields specified in the [query string]:
* `sid=SESSION_ID`: The session id given returned from the [/proxy] call.
* `ack=READ_ACK`: For relays that support support reconnects, this will be the
last read ack the client received. New connections start at `0`.
* `pos=WRITE_ACK`: For relays that support support reconnects, this will be
the last write ack the client received. New connections start at `0`.
* `try=TRY`: For relays that support connection attempt reporting, the number
of times we've tried to connect. New connections start at `1`.
The `SESSION_ID` allows the relay server to validate the connection.
This is why you should always use `wss` instead of `ws` to avoid spoofing.
#### Reads
Incoming data starts with a 4 byte WRITE_ACK ([big endian]).
If the value is larger than 24-bits, it indicates a connection error, and the
connection is shut down.
Otherwise, the server specifies how many bytes it has successfully read, and
the client updates its write queue accordingly.
The rest of the data is binary data from the ssh server.
#### Writes
Outgoing data is broken up into 32KiB chunks (which includes 4 byte ACKs).
The first 4 bytes are a 24-bit READ_ACK ([big endian]) that tells the server
how much data the client has successfully read so far.
Once more than 24-bits worth of data have been read, the value is wrapped.
i.e. The client only cares about the low 24-bits of the count.
The rest of the write is all the data to be sent to the ssh server in binary
form.
The client keeps track of how much data it has written so it can check the
WRITE_ACK sent from the server during reads.
#### Latency Reporting
If the optional ack latency reporting extension is enabled, the client keeps
track of how long it takes for READ_ACKs and WRITE_ACKs to be synchronized,
and then it reports the average inline using the textual form `A:integer`.
Secure Shell maintains a running average over 10 samples, but there is no
requirement as to how often the client reports this information.
There is also reply latency tracking in the form `R:integer`, although Secure
Shell doesn't currently report it.
This is entirely optional and is purely for servers that want to keep track of
performance of client connections.
### /read Protocol (XHR) {#corp-relay-read}
*** aside
Since every read requires setting up a new HTTP connection, and only one read
request may be pending at a time, it's highly recommended you use the
[WebSocket] [/connect] instead.
***
For [XHR] connections, you make a `GET` request to
`RELAY_PROTOCOL://RELAY_HOST:RELAY_PORT/read` to read data from the ssh server.
The [query string] settings:
* `sid=SESSION_ID`: The session id given returned from the [/proxy] call.
* `rcnt=READ_COUNT`: The number of bytes the client has successfully read.
New connections start at `0`.
The `SESSION_ID` allows the relay server to validate the connection.
This is why you should always use `https` instead of `http` to avoid spoofing.
The relay server will send [HTTP 200] with [base64url] encoded data in the
response when there is new data available from the ssh server.
Data might not be available immediately in which case the relay server might
not respond immediately.
It may send [HTTP 410] when the ssh server is no longer reachable.
All other HTTP errors are ignored, and a new `/read` is created for all errors
except for [HTTP 410].
### /write Protocol (XHR) {#corp-relay-write}
*** aside
Since every write requires setting up a new HTTP connection, and only one write
request may be pending at a time, and write chunks are small (typically 1KiB),
it's highly recommended you use the [WebSocket] [/connect] instead.
***
For [XHR] connections, you make a `GET` request to
`RELAY_PROTOCOL://RELAY_HOST:RELAY_PORT/write` to write data to the ssh server.
The fields specified in the [query string]:
* `sid=SESSION_ID`: The session id given returned from the [/proxy] call.
* `wcnt=WRITE_COUNT`: The number of bytes the client has successfully written.
New connections start at `0`.
* `data=DATA`: The data to write to the ssh server. It is [base64url]
encoded. This field is typically limited to 1KiB.
The `SESSION_ID` allows the relay server to validate the connection.
This is why you should always use `https` instead of `http` to avoid spoofing.
The relay server will send [HTTP 200] once the relay server has passed the data
through to the ssh server.
It may send [HTTP 410] when the ssh server is no longer reachable.
All other HTTP errors are ignored.
Data will continue to be sent via new `/write` requests until [HTTP 410] is
returned.
## SSH-FE
This uses the id `ssh-fe@google.com` (e.g. when using `--proxy-mode=`).
TODO
[method field]: #corp-relay-method
[/connect]: #corp-relay-connect
[/cookie]: #corp-relay-cookie
[/proxy]: #corp-relay-proxy
[/read]: #corp-relay-read
[/write]: #corp-relay-write
[base64url]: https://en.wikipedia.org/wiki/Base64#URL_applications
[big endian]: https://en.wikipedia.org/wiki/Endianness
[HTML meta refresh]: https://en.wikipedia.org/wiki/Meta_refresh
[HTTP 200]: https://en.wikipedia.org/wiki/HTTP_200
[HTTP 302]: https://en.wikipedia.org/wiki/HTTP_302
[HTTP 410]: https://en.wikipedia.org/wiki/HTTP_410
[query string]: https://en.wikipedia.org/wiki/Query_string
[URI encoded]: https://en.wikipedia.org/wiki/Percent-encoding
[URI fragment]: https://en.wikipedia.org/wiki/Fragment_identifier
[WebSocket]: https://en.wikipedia.org/wiki/WebSocket
[WebSockets]: https://en.wikipedia.org/wiki/WebSocket
[XHR]: https://en.wikipedia.org/wiki/XMLHttpRequest
[XSSI header]: https://security.stackexchange.com/questions/110539/
[FAQ]: ./FAQ.md
[nassh_google_relay.js]: ../js/nassh_google_relay.js
[nassh_stream_google_relay.js]: ../js/nassh_stream_google_relay.js
[Options]: ./options.md