diff --git a/components/gitpod-cli/go.mod b/components/gitpod-cli/go.mod index 10462dffec72ea..334f1e1752207e 100644 --- a/components/gitpod-cli/go.mod +++ b/components/gitpod-cli/go.mod @@ -15,7 +15,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.7.0 github.com/spf13/cobra v0.0.5 - golang.org/x/sys v0.0.0-20201112073958-5cba982894dd + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 google.golang.org/grpc v1.37.0 gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect diff --git a/components/gitpod-cli/go.sum b/components/gitpod-cli/go.sum index 0cdf51b13cbc92..17413f7affeb92 100644 --- a/components/gitpod-cli/go.sum +++ b/components/gitpod-cli/go.sum @@ -215,6 +215,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -273,6 +274,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -318,6 +320,8 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/components/gitpod-protocol/go/gitpod-service.go b/components/gitpod-protocol/go/gitpod-service.go index 0b085d7b30e806..e4913e816f1a40 100644 --- a/components/gitpod-protocol/go/gitpod-service.go +++ b/components/gitpod-protocol/go/gitpod-service.go @@ -218,9 +218,10 @@ var errNotConnected = errors.New("not connected to Gitpod server") // ConnectToServerOpts configures the server connection type ConnectToServerOpts struct { - Context context.Context - Token string - Log *logrus.Entry + Context context.Context + Token string + Log *logrus.Entry + ReconnectionHandler func() } // ConnectToServer establishes a new websocket connection to the server @@ -248,6 +249,7 @@ func ConnectToServer(endpoint string, opts ConnectToServerOpts) (*APIoverJSONRPC reqHeader.Set("Authorization", "Bearer "+opts.Token) } ws := NewReconnectingWebsocket(endpoint, reqHeader, opts.Log) + ws.ReconnectionHandler = opts.ReconnectionHandler go ws.Dial() var res APIoverJSONRPC diff --git a/components/gitpod-protocol/go/reconnecting-ws.go b/components/gitpod-protocol/go/reconnecting-ws.go index 52efce64c457c5..d7b3613982ac31 100644 --- a/components/gitpod-protocol/go/reconnecting-ws.go +++ b/components/gitpod-protocol/go/reconnecting-ws.go @@ -5,8 +5,11 @@ package protocol import ( + "context" + "encoding/json" "errors" "net/http" + "sync" "time" "github.com/gorilla/websocket" @@ -23,11 +26,14 @@ type ReconnectingWebsocket struct { maxReconnectionDelay time.Duration reconnectionDelayGrowFactor float64 + once sync.Once closedCh chan struct{} - connCh chan chan *websocket.Conn + connCh chan chan *WebsocketConnection errCh chan error log *logrus.Entry + + ReconnectionHandler func() } // NewReconnectingWebsocket creates a new instance of ReconnectingWebsocket @@ -39,7 +45,7 @@ func NewReconnectingWebsocket(url string, reqHeader http.Header, log *logrus.Ent maxReconnectionDelay: 30 * time.Second, reconnectionDelayGrowFactor: 1.5, handshakeTimeout: 2 * time.Second, - connCh: make(chan chan *websocket.Conn), + connCh: make(chan chan *WebsocketConnection), closedCh: make(chan struct{}), errCh: make(chan error), log: log, @@ -48,26 +54,27 @@ func NewReconnectingWebsocket(url string, reqHeader http.Header, log *logrus.Ent // Close closes the underlying webscoket connection. func (rc *ReconnectingWebsocket) Close() error { - close(rc.closedCh) + rc.once.Do(func() { + close(rc.closedCh) + }) return nil } -// WriteObject writes the JSON encoding of v as a message. -// See the documentation for encoding/json Marshal for details about the conversion of Go values to JSON. -func (rc *ReconnectingWebsocket) WriteObject(v interface{}) error { +// EnsureConnection ensures ws connections +// Returns only if connection is permanently failed +// If the passed handler returns false as closed then err is returned to the client, +// otherwise err is treated as a connection error, and new conneciton is provided. +func (rc *ReconnectingWebsocket) EnsureConnection(handler func(conn *WebsocketConnection) (closed bool, err error)) error { for { - connCh := make(chan *websocket.Conn, 1) + connCh := make(chan *WebsocketConnection, 1) select { case <-rc.closedCh: return errors.New("closed") case rc.connCh <- connCh: } conn := <-connCh - err := conn.WriteJSON(v) - if err == nil { - return nil - } - if !websocket.IsUnexpectedCloseError(err) { + closed, err := handler(conn) + if !closed { return err } select { @@ -78,35 +85,62 @@ func (rc *ReconnectingWebsocket) WriteObject(v interface{}) error { } } +func isJSONError(err error) bool { + _, isJsonErr := err.(*json.InvalidUTF8Error) + if isJsonErr { + return true + } + _, isJsonErr = err.(*json.InvalidUnmarshalError) + if isJsonErr { + return true + } + _, isJsonErr = err.(*json.MarshalerError) + if isJsonErr { + return true + } + _, isJsonErr = err.(*json.SyntaxError) + if isJsonErr { + return true + } + _, isJsonErr = err.(*json.UnmarshalFieldError) + if isJsonErr { + return true + } + _, isJsonErr = err.(*json.UnmarshalTypeError) + if isJsonErr { + return true + } + _, isJsonErr = err.(*json.UnsupportedTypeError) + if isJsonErr { + return true + } + _, isJsonErr = err.(*json.UnsupportedValueError) + return isJsonErr +} + +// WriteObject writes the JSON encoding of v as a message. +// See the documentation for encoding/json Marshal for details about the conversion of Go values to JSON. +func (rc *ReconnectingWebsocket) WriteObject(v interface{}) error { + return rc.EnsureConnection(func(conn *WebsocketConnection) (bool, error) { + err := conn.WriteJSON(v) + closed := err != nil && !isJSONError(err) + return closed, err + }) +} + // ReadObject reads the next JSON-encoded message from the connection and stores it in the value pointed to by v. // See the documentation for the encoding/json Unmarshal function for details about the conversion of JSON to a Go value. func (rc *ReconnectingWebsocket) ReadObject(v interface{}) error { - for { - connCh := make(chan *websocket.Conn, 1) - select { - case <-rc.closedCh: - return errors.New("closed") - case rc.connCh <- connCh: - } - conn := <-connCh + return rc.EnsureConnection(func(conn *WebsocketConnection) (bool, error) { err := conn.ReadJSON(v) - if err == nil { - return nil - } - if !websocket.IsUnexpectedCloseError(err) { - return err - } - select { - case <-rc.closedCh: - return errors.New("closed") - case rc.errCh <- err: - } - } + closed := err != nil && !isJSONError(err) + return closed, err + }) } // Dial creates a new client connection. func (rc *ReconnectingWebsocket) Dial() { - var conn *websocket.Conn + var conn *WebsocketConnection defer func() { if conn == nil { return @@ -129,19 +163,26 @@ func (rc *ReconnectingWebsocket) Dial() { time.Sleep(1 * time.Second) conn = rc.connect() + if conn != nil && rc.ReconnectionHandler != nil { + go rc.ReconnectionHandler() + } } } } -func (rc *ReconnectingWebsocket) connect() *websocket.Conn { +func (rc *ReconnectingWebsocket) connect() *WebsocketConnection { delay := rc.minReconnectionDelay for { dialer := websocket.Dialer{HandshakeTimeout: rc.handshakeTimeout} conn, _, err := dialer.Dial(rc.url, rc.reqHeader) if err == nil { rc.log.WithField("url", rc.url).Info("connection was successfully established") - - return conn + ws, err := NewWebsocketConnection(context.Background(), conn, func(staleErr error) { + rc.errCh <- staleErr + }) + if err == nil { + return ws + } } rc.log.WithError(err).WithField("url", rc.url).Errorf("failed to connect, trying again in %d seconds...", uint32(delay.Seconds())) diff --git a/components/gitpod-protocol/go/websocket.go b/components/gitpod-protocol/go/websocket.go new file mode 100644 index 00000000000000..a16ff443d329cb --- /dev/null +++ b/components/gitpod-protocol/go/websocket.go @@ -0,0 +1,139 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2020 Jaime Pillora . All rights reserved. + * Licensed under the MIT License. See https://github.com/jpillora/chisel/blob/7aa0da95db178b8bc4f20ab49128368348fd4410/LICENSE for license information. + *--------------------------------------------------------------------------------------------*/ +// copied and modified from https://github.com/jpillora/chisel/blob/33fa2010abd42ec76ed9011995f5240642b1a3c5/share/cnet/conn_ws.go +package protocol + +import ( + "context" + "sync" + "time" + + "github.com/gorilla/websocket" +) + +type WebsocketConnection struct { + *websocket.Conn + buff []byte + + Ctx context.Context + cancel func() + + once sync.Once + closeErr error + waitDone chan struct{} +} + +var ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 15 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 +) + +//NewWebsocketConnection converts a websocket.Conn into a net.Conn +func NewWebsocketConnection(ctx context.Context, websocketConn *websocket.Conn, onStale func(staleErr error)) (*WebsocketConnection, error) { + ctx, cancel := context.WithCancel(ctx) + c := &WebsocketConnection{ + Conn: websocketConn, + waitDone: make(chan struct{}), + Ctx: ctx, + cancel: cancel, + } + err := c.SetReadDeadline(time.Now().Add(pongWait)) + if err != nil { + return nil, err + } + c.SetPongHandler(func(string) error { c.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + + go func() { + defer c.Close() + ticker := time.NewTicker(pingPeriod) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + staleErr := c.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait)) + if staleErr != nil { + onStale(staleErr) + return + } + } + } + }() + return c, nil +} + +// Close closes the connection +func (c *WebsocketConnection) Close() error { + c.once.Do(func() { + c.cancel() + c.closeErr = c.Conn.Close() + close(c.waitDone) + }) + return c.closeErr +} + +// Wait waits till the connection is closed. +func (c *WebsocketConnection) Wait() error { + <-c.waitDone + return c.closeErr +} + +//Read is not threadsafe though thats okay since there +//should never be more than one reader +func (c *WebsocketConnection) Read(dst []byte) (int, error) { + ldst := len(dst) + //use buffer or read new message + var src []byte + if len(c.buff) > 0 { + src = c.buff + c.buff = nil + } else if _, msg, err := c.Conn.ReadMessage(); err == nil { + src = msg + } else { + return 0, err + } + //copy src->dest + var n int + if len(src) > ldst { + //copy as much as possible of src into dst + n = copy(dst, src[:ldst]) + //copy remainder into buffer + r := src[ldst:] + lr := len(r) + c.buff = make([]byte, lr) + copy(c.buff, r) + } else { + //copy all of src into dst + n = copy(dst, src) + } + //return bytes copied + return n, nil +} + +func (c *WebsocketConnection) Write(b []byte) (int, error) { + err := c.Conn.WriteMessage(websocket.BinaryMessage, b) + if err != nil { + return 0, err + } + n := len(b) + return n, nil +} + +func (c *WebsocketConnection) SetDeadline(t time.Time) error { + if err := c.Conn.SetReadDeadline(t); err != nil { + return err + } + return c.Conn.SetWriteDeadline(t) +} diff --git a/components/local-app/go.mod b/components/local-app/go.mod index 75554e745a9d70..ca433343df750a 100644 --- a/components/local-app/go.mod +++ b/components/local-app/go.mod @@ -5,13 +5,17 @@ go 1.16 require ( github.com/gitpod-io/gitpod/gitpod-protocol v0.0.0-00010101000000-000000000000 github.com/gitpod-io/gitpod/supervisor/api v0.0.0-00010101000000-000000000000 - github.com/jpillora/chisel v1.7.6 + github.com/google/uuid v1.1.2 + github.com/gorilla/handlers v1.5.1 + github.com/gorilla/websocket v1.4.2 // indirect github.com/kevinburke/ssh_config v1.1.0 github.com/sirupsen/logrus v1.7.0 github.com/urfave/cli/v2 v2.3.0 github.com/zalando/go-keyring v0.1.1 + golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 // indirect google.golang.org/grpc v1.37.0 + google.golang.org/protobuf v1.26.0 // indirect ) replace github.com/gitpod-io/gitpod/gitpod-protocol => ../gitpod-protocol/go // leeway diff --git a/components/local-app/go.sum b/components/local-app/go.sum index df340a4a3291f8..9297981a35fa82 100644 --- a/components/local-app/go.sum +++ b/components/local-app/go.sum @@ -57,6 +57,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -117,9 +119,12 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -131,8 +136,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/jpillora/ansi v1.0.2/go.mod h1:D2tT+6uzJvN1nBVQILYWkIdq7zG+b5gcFN5WI/VyjMY= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jpillora/chisel v1.7.6 h1:ipVmcsJcz90+0u0rkn07wKwcXNiw1W/3E2w3rxIPpWM= -github.com/jpillora/chisel v1.7.6/go.mod h1:BC2zg11mTIoyGPUjc2EkTgfz3uUUV93+K9tNYCCU/fw= +github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+PpqnyA= github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8= github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw= github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0= @@ -185,6 +189,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -242,6 +248,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -285,8 +293,10 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/components/local-app/main.go b/components/local-app/main.go index 0165f7af852fd5..76c7b08beeb6ef 100644 --- a/components/local-app/main.go +++ b/components/local-app/main.go @@ -9,13 +9,19 @@ import ( "context" "errors" + "fmt" "log" + "net/http" + "net/url" "os" + "regexp" + "strconv" "strings" gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" "github.com/gitpod-io/local-app/pkg/auth" "github.com/gitpod-io/local-app/pkg/bastion" + "github.com/gorilla/handlers" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" "github.com/zalando/go-keyring" @@ -47,6 +53,10 @@ func main() { Name: "mock-keyring", Usage: "Don't use system native keyring, but store Gitpod token in memory", }, + &cli.BoolFlag{ + Name: "allow-cors-from-port", + Usage: "Allow CORS requests from workspace port location", + }, }, Commands: []*cli.Command{ { @@ -55,7 +65,7 @@ func main() { if c.Bool("mock-keyring") { keyring.MockInit() } - return run(c.String("gitpod-host"), c.String("ssh_config")) + return run(c.String("gitpod-host"), c.String("ssh_config"), c.Bool("allow-cors-from-port")) }, Flags: []cli.Flag{ &cli.PathFlag{ @@ -79,15 +89,29 @@ func DefaultCommand(name string) cli.ActionFunc { } } -func run(host, sshConfig string) error { - tkn, err := auth.GetToken(host) +func run(origin, sshConfig string, allowCORSFromPort bool) error { + tkn, err := auth.GetToken(origin) if errors.Is(err, keyring.ErrNotFound) { - tkn, err = auth.Login(context.Background(), auth.LoginOpts{GitpodURL: host}) + tkn, err = auth.Login(context.Background(), auth.LoginOpts{GitpodURL: origin}) } if err != nil { return err } + originURL, err := url.Parse(origin) + if err != nil { + return err + } + wsHostRegex := "(\\.[^.]+)\\." + strings.ReplaceAll(originURL.Host, ".", "\\.") + wsHostRegex = "([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-z]{2,16}-[0-9a-z]{2,16}-[0-9a-z]{8})" + wsHostRegex + if allowCORSFromPort { + wsHostRegex = "([0-9]+)-" + wsHostRegex + } + hostRegex, err := regexp.Compile("^" + wsHostRegex) + if err != nil { + return err + } + cb := bastion.CompositeCallbacks{ &logCallbacks{}, } @@ -95,7 +119,9 @@ func run(host, sshConfig string) error { cb = append(cb, &bastion.SSHConfigWritingCallback{Path: sshConfig}) } - wshost := host + var b *bastion.Bastion + + wshost := origin wshost = strings.ReplaceAll(wshost, "https://", "wss://") wshost = strings.ReplaceAll(wshost, "http://", "ws://") wshost += "/api/v1" @@ -103,11 +129,46 @@ func run(host, sshConfig string) error { Context: context.Background(), Token: tkn, Log: logrus.NewEntry(logrus.New()), + ReconnectionHandler: func() { + if b != nil { + b.FullUpdate() + } + }, }) if err != nil { return err } - b := bastion.New(client, cb) + + b = bastion.New(client, cb) + go http.ListenAndServe("localhost:5000", handlers.CORS( + handlers.AllowedOriginValidator(func(origin string) bool { + url, err := url.Parse(origin) + if err != nil { + return false + } + // Is the origin a subdomain of the installations hostname? + matches := hostRegex.Match([]byte(url.Host)) + return matches + }), + )(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + segs := strings.Split(r.URL.Path, "/") + if len(segs) < 3 { + http.Error(rw, "invalid URL Path", http.StatusBadRequest) + return + } + worksapceID := segs[1] + port, err := strconv.Atoi(segs[2]) + if err != nil { + http.Error(rw, err.Error(), http.StatusBadRequest) + return + } + localAddr, err := b.GetTunnelLocalAddr(worksapceID, uint32(port)) + if err != nil { + http.Error(rw, err.Error(), http.StatusNotFound) + return + } + fmt.Fprintf(rw, localAddr) + }))) return b.Run() } diff --git a/components/local-app/pkg/bastion/bastion.go b/components/local-app/pkg/bastion/bastion.go index 0250184d72ef96..27e95e7aca2fcd 100644 --- a/components/local-app/pkg/bastion/bastion.go +++ b/components/local-app/pkg/bastion/bastion.go @@ -7,6 +7,7 @@ package bastion import ( "context" "fmt" + "io" "io/ioutil" "net" "net/http" @@ -14,28 +15,52 @@ import ( "path/filepath" "strconv" "strings" + "sync" "time" gitpod "github.com/gitpod-io/gitpod/gitpod-protocol" supervisor "github.com/gitpod-io/gitpod/supervisor/api" + "github.com/google/uuid" "github.com/kevinburke/ssh_config" + "golang.org/x/crypto/ssh" "google.golang.org/grpc" + "google.golang.org/protobuf/proto" - chisel "github.com/jpillora/chisel/client" "github.com/sirupsen/logrus" ) +type TunnelClient struct { + ID string // we cannot use conn session ID, since proto fails to serialize it + Conn ssh.Conn +} + +type TunnelListener struct { + LocalAddr string + Visibility supervisor.TunnelVisiblity + Ctx context.Context + Cancel func() +} + type Workspace struct { WorkspaceID string Phase string OwnerToken string URL string - LocalSupervisorPort int - LocalSSHPort int + supervisorListener *TunnelListener + supervisorClient *grpc.ClientConn + + tunnelListenersMu sync.RWMutex + tunnelListeners map[uint32]*TunnelListener + + localSSHListener *TunnelListener ctx context.Context cancel context.CancelFunc + + tunnelClient chan chan *TunnelClient + tunnelClientConnected bool + portsTunneled bool } type Callbacks interface { @@ -60,7 +85,7 @@ func (s *SSHConfigWritingCallback) InstanceUpdate(w *Workspace) { if s.workspaces == nil { s.workspaces = make(map[string]*Workspace) } - if w.LocalSSHPort == 0 || w.Phase == "stopping" { + if w.localSSHListener == nil || w.Phase == "stopping" { delete(s.workspaces, w.WorkspaceID) } else { s.workspaces[w.WorkspaceID] = w @@ -71,12 +96,13 @@ func (s *SSHConfigWritingCallback) InstanceUpdate(w *Workspace) { // TODO(cw): don't ignore error p, _ := ssh_config.NewPattern(ws.WorkspaceID) + host, port, _ := net.SplitHostPort(ws.localSSHListener.LocalAddr) cfg.Hosts = append(cfg.Hosts, &ssh_config.Host{ Patterns: []*ssh_config.Pattern{p}, Nodes: []ssh_config.Node{ - &ssh_config.KV{Key: "HostName", Value: "127.0.0.1"}, + &ssh_config.KV{Key: "HostName", Value: host}, &ssh_config.KV{Key: "User", Value: "gitpod"}, - &ssh_config.KV{Key: "Port", Value: strconv.Itoa(ws.LocalSSHPort)}, + &ssh_config.KV{Key: "Port", Value: port}, }, }) } @@ -96,14 +122,19 @@ func New(client gitpod.APIInterface, cb Callbacks) *Bastion { workspaces: make(map[string]*Workspace), ctx: ctx, stop: cancel, + updates: make(chan *gitpod.WorkspaceInstance, 10), } } type Bastion struct { + id string + updates chan *gitpod.WorkspaceInstance + Client gitpod.APIInterface Callbacks Callbacks - workspaces map[string]*Workspace + workspacesMu sync.RWMutex + workspaces map[string]*Workspace ctx context.Context stop context.CancelFunc @@ -115,17 +146,20 @@ func (b *Bastion) Run() error { return err } - uchan := make(chan *gitpod.WorkspaceInstance, 10) - go b.acceptUpdates(uchan) - b.fullUpdate(uchan) + go func() { + for u := range b.updates { + b.handleUpdate(u) + } + }() + b.FullUpdate() for u := range updates { - uchan <- u + b.updates <- u } return nil } -func (b *Bastion) fullUpdate(uchan chan<- *gitpod.WorkspaceInstance) { +func (b *Bastion) FullUpdate() { wss, err := b.Client.GetWorkspaces(b.ctx, &gitpod.GetWorkspacesOptions{Limit: float64(100)}) if err != nil { logrus.WithError(err).Warn("cannot get workspaces") @@ -134,109 +168,278 @@ func (b *Bastion) fullUpdate(uchan chan<- *gitpod.WorkspaceInstance) { if ws.LatestInstance == nil { continue } - uchan <- ws.LatestInstance + b.updates <- ws.LatestInstance } } } -func (b *Bastion) acceptUpdates(updates chan *gitpod.WorkspaceInstance) { - for u := range updates { - ws, ok := b.workspaces[u.ID] - if !ok { - if u.Status.Phase == "stopping" || u.Status.Phase == "stopped" { - continue - } - ctx, cancel := context.WithCancel(b.ctx) - ws = &Workspace{ - WorkspaceID: u.WorkspaceID, +func (b *Bastion) handleUpdate(u *gitpod.WorkspaceInstance) { + b.workspacesMu.Lock() + defer b.workspacesMu.Unlock() - ctx: ctx, - cancel: cancel, - } - } - ws.Phase = u.Status.Phase - ws.URL = u.IdeURL - ws.OwnerToken = u.Status.OwnerToken - if ws.OwnerToken == "" && ws.Phase == "running" { - // updates don't carry the owner token - go b.fullUpdate(updates) + ws, ok := b.workspaces[u.ID] + if !ok { + if u.Status.Phase == "stopping" || u.Status.Phase == "stopped" { + return } + ctx, cancel := context.WithCancel(b.ctx) + ws = &Workspace{ + WorkspaceID: u.WorkspaceID, - switch ws.Phase { - case "running": - if ws.LocalSupervisorPort == 0 && ws.OwnerToken != "" { - var err error - ws.LocalSupervisorPort, err = b.establishChiselTunnel(ws, "supervisor", 22999) - if err != nil { - logrus.WithError(err).Error("cannot establish supervisor tunnel") - } + ctx: ctx, + cancel: cancel, + + tunnelClient: make(chan chan *TunnelClient, 1), + tunnelListeners: make(map[uint32]*TunnelListener), + } + } + ws.Phase = u.Status.Phase + ws.URL = u.IdeURL + ws.OwnerToken = u.Status.OwnerToken + if ws.OwnerToken == "" && ws.Phase == "running" { + // updates don't carry the owner token + go b.FullUpdate() + } - logrus.WithField("workspace", ws.WorkspaceID).WithField("supervisor port", ws.LocalSupervisorPort).Info("established supervisor channel") + switch ws.Phase { + case "running": + if ws.OwnerToken != "" && !ws.tunnelClientConnected { + err := b.connectTunnelClient(ws.ctx, ws) + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).Error("tunnel client failed to connect") + } + } + if ws.supervisorListener == nil && ws.tunnelClientConnected { + var err error + ws.supervisorListener, err = b.establishTunnel(ws.ctx, ws, "supervisor", 22999, 0, supervisor.TunnelVisiblity_host) + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).Error("cannot establish supervisor tunnel") } + } - if ws.LocalSSHPort == 0 && ws.LocalSupervisorPort != 0 { - var err error - ws.LocalSSHPort, err = b.establishSSHTunnel(ws) - if err != nil { - logrus.WithError(err).Error("cannot establish SSH tunnel") - } + if ws.supervisorClient == nil && ws.supervisorListener != nil { + var err error + ws.supervisorClient, err = grpc.Dial(ws.supervisorListener.LocalAddr, grpc.WithInsecure()) + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).Print("error connecting to supervisor") + } else { + go func() { + <-ws.ctx.Done() + ws.supervisorClient.Close() + }() } + } - case "stopping", "stopped": - ws.cancel() - delete(b.workspaces, ws.WorkspaceID) - b.Callbacks.InstanceUpdate(ws) - continue + if ws.supervisorClient != nil && !ws.portsTunneled { + ws.portsTunneled = true + go b.tunnelPorts(ws) } - b.workspaces[u.ID] = ws + if ws.localSSHListener == nil && ws.supervisorClient != nil { + var err error + ws.localSSHListener, err = b.establishSSHTunnel(ws) + if err != nil { + logrus.WithError(err).Error("cannot establish SSH tunnel") + } + } + + case "stopping", "stopped": + ws.cancel() + delete(b.workspaces, ws.WorkspaceID) b.Callbacks.InstanceUpdate(ws) + return } + + b.workspaces[u.ID] = ws + b.Callbacks.InstanceUpdate(ws) } -func (b *Bastion) establishChiselTunnel(ws *Workspace, logprefix string, remotePort int) (localPort int, err error) { +func (b *Bastion) connectTunnelClient(ctx context.Context, ws *Workspace) error { if ws.URL == "" { - return 0, fmt.Errorf("IDE URL is empty") + return fmt.Errorf("IDE URL is empty") } if ws.OwnerToken == "" { - return 0, fmt.Errorf("owner token is empty") + return fmt.Errorf("owner token is empty") } - - l, err := net.Listen("tcp", "localhost:0") - if err != nil { - return 0, err + if ws.tunnelClientConnected { + return fmt.Errorf("tunnel: ssh client is already connected") } - localPort = l.(*net.TCPListener).Addr().(*net.TCPAddr).Port - l.Close() + ws.tunnelClientConnected = true + tunnelURL := ws.URL + tunnelURL = strings.ReplaceAll(tunnelURL, "https://", "wss://") + tunnelURL = strings.ReplaceAll(tunnelURL, "http://", "ws://") + tunnelURL += "/_supervisor/tunnel" h := make(http.Header) h.Set("x-gitpod-owner-token", ws.OwnerToken) - cl, err := chisel.NewClient(&chisel.Config{ - KeepAlive: 10 * time.Second, - Headers: h, - Server: ws.URL + "/_supervisor", - Remotes: []string{fmt.Sprintf("%d:%d", localPort, remotePort)}, + webSocket := gitpod.NewReconnectingWebsocket(tunnelURL, h, logrus.WithField("workspace", ws.WorkspaceID)) + go webSocket.Dial() + go func() { + var ( + client *TunnelClient + err error + ) + defer func() { + ws.tunnelClientConnected = false + webSocket.Close() + if err != nil { + logrus.WithField("workspace", ws.WorkspaceID).WithError(err).Error("tunnel: failed to connect ssh client") + } + if client != nil { + logrus.WithField("workspace", ws.WorkspaceID).WithField("id", client.ID).Warn("tunnel: ssh client is permanently closed") + } + }() + client, closed, err := newTunnelClient(ctx, ws, webSocket) + for { + if err != nil { + return + } + select { + case <-ctx.Done(): + return + case clientCh := <-ws.tunnelClient: + clientCh <- client + case <-closed: + client, closed, err = newTunnelClient(ctx, ws, webSocket) + } + } + }() + return nil +} + +func newTunnelClient(ctx context.Context, ws *Workspace, reconnecting *gitpod.ReconnectingWebsocket) (client *TunnelClient, closed chan struct{}, err error) { + logrus.WithField("workspace", ws.WorkspaceID).Info("tunnel: trying to connect ssh client...") + err = reconnecting.EnsureConnection(func(conn *gitpod.WebsocketConnection) (bool, error) { + id, err := uuid.NewRandom() + if err != nil { + return false, err + } + + sshConn, chans, reqs, err := ssh.NewClientConn(conn, "", &ssh.ClientConfig{ + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }) + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).Warn("tunnel: failed to connect ssh client, trying again...") + return true, err + } + logrus.WithField("workspace", ws.WorkspaceID).WithField("id", id).Info("tunnel: ssh client connected") + go func() { + conn.Wait() + sshConn.Close() + }() + go ssh.DiscardRequests(reqs) + go func() { + for newCh := range chans { + // TODO(ak) reverse tunneling + newCh.Reject(ssh.UnknownChannelType, "tunnel: reverse is not supported yet") + } + }() + closed = make(chan struct{}, 1) + go func() { + err := sshConn.Wait() + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).WithField("id", id).Warn("tunnel: ssh client closed") + close(closed) + }() + client = &TunnelClient{ + ID: id.String(), + Conn: sshConn, + } + return false, nil }) - cl.Logger = cl.Logger.Fork(logprefix) - if err != nil { - return 0, err + return client, closed, err +} + +func (b *Bastion) establishTunnel(ctx context.Context, ws *Workspace, logprefix string, remotePort int, targetPort int, visibility supervisor.TunnelVisiblity) (*TunnelListener, error) { + if !ws.tunnelClientConnected { + return nil, fmt.Errorf("tunnel client is not connected") } - err = cl.Start(ws.ctx) - if err != nil { - return 0, err + if visibility == supervisor.TunnelVisiblity_none { + return nil, fmt.Errorf("tunnel visibility is none") + } + + targetHost := "127.0.0.1" + if visibility == supervisor.TunnelVisiblity_network { + targetHost = "0.0.0.0" + } + + netListener, err := net.Listen("tcp", targetHost+":"+strconv.Itoa(targetPort)) + var localPort int + if err == nil { + localPort = netListener.(*net.TCPListener).Addr().(*net.TCPAddr).Port + } else { + netListener, err = net.Listen("tcp", targetHost+":0") + if err != nil { + return nil, err + } + localPort = netListener.(*net.TCPListener).Addr().(*net.TCPAddr).Port } + logrus.WithField("workspace", ws.WorkspaceID).Info(logprefix + ": listening on " + netListener.Addr().String() + "...") + listenerCtx, cancel := context.WithCancel(ctx) + go func() { + <-listenerCtx.Done() + netListener.Close() + logrus.WithField("workspace", ws.WorkspaceID).Info(logprefix + ": closed") + }() go func() { - for ws.ctx.Err() == nil { - _ = cl.Wait() - time.Sleep(100 * time.Millisecond) - logrus.Debugf("reconnecting %s", logprefix) - _ = cl.Start(ws.ctx) + for { + conn, err := netListener.Accept() + if listenerCtx.Err() != nil { + return + } + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).Warn(logprefix + ": failed to accept connection") + continue + } + go func() { + defer conn.Close() + + clientCh := make(chan *TunnelClient, 1) + select { + case <-listenerCtx.Done(): + return + case ws.tunnelClient <- clientCh: + } + client := <-clientCh + + payload, err := proto.Marshal(&supervisor.TunnelPortRequest{ + ClientId: client.ID, + Port: uint32(remotePort), + TargetPort: uint32(localPort), + }) + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).WithField("id", client.ID).Error(logprefix + ": failed to marshal tunnel payload") + return + } + sshChan, reqs, err := client.Conn.OpenChannel("tunnel", payload) + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).WithField("id", client.ID).Warn(logprefix + ": failed to establish tunnel") + return + } + defer sshChan.Close() + go ssh.DiscardRequests(reqs) + var wg sync.WaitGroup + wg.Add(2) + go func() { + _, _ = io.Copy(sshChan, conn) + wg.Done() + }() + go func() { + _, _ = io.Copy(conn, sshChan) + wg.Done() + }() + wg.Wait() + }() } }() - return + return &TunnelListener{ + LocalAddr: netListener.Addr().String(), + Visibility: visibility, + Ctx: listenerCtx, + Cancel: cancel, + }, nil } -func (b *Bastion) establishSSHTunnel(ws *Workspace) (localPort int, err error) { +func (b *Bastion) establishSSHTunnel(ws *Workspace) (listener *TunnelListener, err error) { key, err := readPublicSSHKey() if err != nil { // TODO(cw): surface to the user and ask them to run ssh-keygen @@ -247,8 +450,8 @@ func (b *Bastion) establishSSHTunnel(ws *Workspace) (localPort int, err error) { // TODO(cw): surface to the user and ask them install the key manually logrus.WithError(err).Warn("cannot install authorized key") } - - return b.establishChiselTunnel(ws, "ssh", 23001) + listener, err = b.establishTunnel(ws.ctx, ws, "ssh", 23001, 0, supervisor.TunnelVisiblity_host) + return listener, err } func readPublicSSHKey() (key string, err error) { @@ -264,14 +467,9 @@ func readPublicSSHKey() (key string, err error) { } func installSSHAuthorizedKey(ws *Workspace, key string) error { - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", ws.LocalSupervisorPort), grpc.WithInsecure()) - if err != nil { - return err - } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - term := supervisor.NewTerminalServiceClient(conn) + term := supervisor.NewTerminalServiceClient(ws.supervisorClient) tres, err := term.Open(ctx, &supervisor.OpenTerminalRequest{Workdir: "/", Shell: "/bin/sh"}) if err != nil { return err @@ -293,3 +491,111 @@ func installSSHAuthorizedKey(ws *Workspace, key string) error { return nil } + +func (b *Bastion) GetTunnelLocalAddr(workspaceID string, port uint32) (string, error) { + ws, ok := b.getWorkspace(workspaceID) + if !ok { + return "", fmt.Errorf("workspace not running") + } + + ws.tunnelListenersMu.RLock() + defer ws.tunnelListenersMu.RUnlock() + listener, ok := ws.tunnelListeners[port] + if !ok { + return "", fmt.Errorf("port not tunneled") + } + return listener.LocalAddr, nil +} + +func (b *Bastion) getWorkspace(workspaceID string) (*Workspace, bool) { + b.workspacesMu.RLock() + defer b.workspacesMu.RUnlock() + ws, ok := b.workspaces[workspaceID] + return ws, ok +} + +func (b *Bastion) tunnelPorts(ws *Workspace) { + defer func() { + ws.portsTunneled = false + logrus.WithField("workspace", ws.WorkspaceID).Info("ports tunneling finished") + }() + for { + logrus.WithField("workspace", ws.WorkspaceID).Info("tunneling ports...") + err := b.doTunnelPorts(ws) + if ws.ctx.Err() != nil { + return + } + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).Warn("ports tunneling failed, retrying...") + } + select { + case <-ws.ctx.Done(): + return + case <-time.After(1 * time.Second): + } + } +} +func (b *Bastion) doTunnelPorts(ws *Workspace) error { + statusService := supervisor.NewStatusServiceClient(ws.supervisorClient) + status, err := statusService.PortsStatus(ws.ctx, &supervisor.PortsStatusRequest{ + Observe: true, + }) + if err != nil { + return err + } + defer func() { + ws.tunnelListenersMu.Lock() + defer ws.tunnelListenersMu.Unlock() + for port, t := range ws.tunnelListeners { + delete(ws.tunnelListeners, port) + t.Cancel() + } + }() + for { + resp, err := status.Recv() + if err != nil { + return err + } + ws.tunnelListenersMu.Lock() + currentTunneled := make(map[uint32]struct{}) + for _, port := range resp.Ports { + visibility := supervisor.TunnelVisiblity_none + if port.Tunneled != nil { + visibility = port.Tunneled.Visibility + } + listener, alreadyTunneled := ws.tunnelListeners[port.LocalPort] + if alreadyTunneled && listener.Visibility != visibility { + listener.Cancel() + delete(ws.tunnelListeners, port.LocalPort) + } + if visibility == supervisor.TunnelVisiblity_none { + continue + } + currentTunneled[port.LocalPort] = struct{}{} + _, alreadyTunneled = ws.tunnelListeners[port.LocalPort] + if alreadyTunneled { + continue + } + _, alreadyTunneled = port.Tunneled.Clients[b.id] + if alreadyTunneled { + continue + } + + logprefix := "tunnel[" + supervisor.TunnelVisiblity_name[int32(port.Tunneled.Visibility)] + ":" + strconv.Itoa(int(port.LocalPort)) + "]" + listener, err := b.establishTunnel(ws.ctx, ws, logprefix, int(port.LocalPort), int(port.Tunneled.TargetPort), port.Tunneled.Visibility) + if err != nil { + logrus.WithError(err).WithField("workspace", ws.WorkspaceID).WithField("port", port.LocalPort).Error("cannot establish port tunnel") + } else { + ws.tunnelListeners[port.LocalPort] = listener + } + } + for port, listener := range ws.tunnelListeners { + _, exists := currentTunneled[port] + if !exists { + delete(ws.tunnelListeners, port) + listener.Cancel() + } + } + ws.tunnelListenersMu.Unlock() + } +} diff --git a/components/supervisor-api/go/port.pb.go b/components/supervisor-api/go/port.pb.go new file mode 100644 index 00000000000000..83a209bbe161e9 --- /dev/null +++ b/components/supervisor-api/go/port.pb.go @@ -0,0 +1,725 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.5 +// source: port.proto + +package api + +import ( + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TunnelVisiblity int32 + +const ( + TunnelVisiblity_none TunnelVisiblity = 0 + TunnelVisiblity_host TunnelVisiblity = 1 + TunnelVisiblity_network TunnelVisiblity = 2 +) + +// Enum value maps for TunnelVisiblity. +var ( + TunnelVisiblity_name = map[int32]string{ + 0: "none", + 1: "host", + 2: "network", + } + TunnelVisiblity_value = map[string]int32{ + "none": 0, + "host": 1, + "network": 2, + } +) + +func (x TunnelVisiblity) Enum() *TunnelVisiblity { + p := new(TunnelVisiblity) + *p = x + return p +} + +func (x TunnelVisiblity) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TunnelVisiblity) Descriptor() protoreflect.EnumDescriptor { + return file_port_proto_enumTypes[0].Descriptor() +} + +func (TunnelVisiblity) Type() protoreflect.EnumType { + return &file_port_proto_enumTypes[0] +} + +func (x TunnelVisiblity) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TunnelVisiblity.Descriptor instead. +func (TunnelVisiblity) EnumDescriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{0} +} + +type TunnelPortRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Port uint32 `protobuf:"varint,1,opt,name=port,proto3" json:"port,omitempty"` + TargetPort uint32 `protobuf:"varint,2,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` + Visibility TunnelVisiblity `protobuf:"varint,3,opt,name=visibility,proto3,enum=supervisor.TunnelVisiblity" json:"visibility,omitempty"` + ClientId string `protobuf:"bytes,4,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` +} + +func (x *TunnelPortRequest) Reset() { + *x = TunnelPortRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_port_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TunnelPortRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TunnelPortRequest) ProtoMessage() {} + +func (x *TunnelPortRequest) ProtoReflect() protoreflect.Message { + mi := &file_port_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TunnelPortRequest.ProtoReflect.Descriptor instead. +func (*TunnelPortRequest) Descriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{0} +} + +func (x *TunnelPortRequest) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +func (x *TunnelPortRequest) GetTargetPort() uint32 { + if x != nil { + return x.TargetPort + } + return 0 +} + +func (x *TunnelPortRequest) GetVisibility() TunnelVisiblity { + if x != nil { + return x.Visibility + } + return TunnelVisiblity_none +} + +func (x *TunnelPortRequest) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +type TunnelPortResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *TunnelPortResponse) Reset() { + *x = TunnelPortResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_port_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TunnelPortResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TunnelPortResponse) ProtoMessage() {} + +func (x *TunnelPortResponse) ProtoReflect() protoreflect.Message { + mi := &file_port_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TunnelPortResponse.ProtoReflect.Descriptor instead. +func (*TunnelPortResponse) Descriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{1} +} + +type CloseTunnelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Port uint32 `protobuf:"varint,1,opt,name=port,proto3" json:"port,omitempty"` +} + +func (x *CloseTunnelRequest) Reset() { + *x = CloseTunnelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_port_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseTunnelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseTunnelRequest) ProtoMessage() {} + +func (x *CloseTunnelRequest) ProtoReflect() protoreflect.Message { + mi := &file_port_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseTunnelRequest.ProtoReflect.Descriptor instead. +func (*CloseTunnelRequest) Descriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{2} +} + +func (x *CloseTunnelRequest) GetPort() uint32 { + if x != nil { + return x.Port + } + return 0 +} + +type CloseTunnelResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CloseTunnelResponse) Reset() { + *x = CloseTunnelResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_port_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CloseTunnelResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CloseTunnelResponse) ProtoMessage() {} + +func (x *CloseTunnelResponse) ProtoReflect() protoreflect.Message { + mi := &file_port_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CloseTunnelResponse.ProtoReflect.Descriptor instead. +func (*CloseTunnelResponse) Descriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{3} +} + +type EstablishTunnelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Output: + // *EstablishTunnelRequest_Desc + // *EstablishTunnelRequest_Data + Output isEstablishTunnelRequest_Output `protobuf_oneof:"output"` +} + +func (x *EstablishTunnelRequest) Reset() { + *x = EstablishTunnelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_port_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EstablishTunnelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EstablishTunnelRequest) ProtoMessage() {} + +func (x *EstablishTunnelRequest) ProtoReflect() protoreflect.Message { + mi := &file_port_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EstablishTunnelRequest.ProtoReflect.Descriptor instead. +func (*EstablishTunnelRequest) Descriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{4} +} + +func (m *EstablishTunnelRequest) GetOutput() isEstablishTunnelRequest_Output { + if m != nil { + return m.Output + } + return nil +} + +func (x *EstablishTunnelRequest) GetDesc() *TunnelPortRequest { + if x, ok := x.GetOutput().(*EstablishTunnelRequest_Desc); ok { + return x.Desc + } + return nil +} + +func (x *EstablishTunnelRequest) GetData() []byte { + if x, ok := x.GetOutput().(*EstablishTunnelRequest_Data); ok { + return x.Data + } + return nil +} + +type isEstablishTunnelRequest_Output interface { + isEstablishTunnelRequest_Output() +} + +type EstablishTunnelRequest_Desc struct { + Desc *TunnelPortRequest `protobuf:"bytes,1,opt,name=desc,proto3,oneof"` +} + +type EstablishTunnelRequest_Data struct { + Data []byte `protobuf:"bytes,2,opt,name=data,proto3,oneof"` +} + +func (*EstablishTunnelRequest_Desc) isEstablishTunnelRequest_Output() {} + +func (*EstablishTunnelRequest_Data) isEstablishTunnelRequest_Output() {} + +type EstablishTunnelResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *EstablishTunnelResponse) Reset() { + *x = EstablishTunnelResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_port_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EstablishTunnelResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EstablishTunnelResponse) ProtoMessage() {} + +func (x *EstablishTunnelResponse) ProtoReflect() protoreflect.Message { + mi := &file_port_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EstablishTunnelResponse.ProtoReflect.Descriptor instead. +func (*EstablishTunnelResponse) Descriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{5} +} + +func (x *EstablishTunnelResponse) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type AutoTunnelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` +} + +func (x *AutoTunnelRequest) Reset() { + *x = AutoTunnelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_port_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AutoTunnelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AutoTunnelRequest) ProtoMessage() {} + +func (x *AutoTunnelRequest) ProtoReflect() protoreflect.Message { + mi := &file_port_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AutoTunnelRequest.ProtoReflect.Descriptor instead. +func (*AutoTunnelRequest) Descriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{6} +} + +func (x *AutoTunnelRequest) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +type AutoTunnelResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *AutoTunnelResponse) Reset() { + *x = AutoTunnelResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_port_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AutoTunnelResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AutoTunnelResponse) ProtoMessage() {} + +func (x *AutoTunnelResponse) ProtoReflect() protoreflect.Message { + mi := &file_port_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AutoTunnelResponse.ProtoReflect.Descriptor instead. +func (*AutoTunnelResponse) Descriptor() ([]byte, []int) { + return file_port_proto_rawDescGZIP(), []int{7} +} + +var File_port_proto protoreflect.FileDescriptor + +var file_port_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x73, 0x75, + 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa2, 0x01, 0x0a, 0x11, 0x54, 0x75, 0x6e, 0x6e, 0x65, + 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x50, 0x6f, 0x72, + 0x74, 0x12, 0x3b, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, + 0x6f, 0x72, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x69, + 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x1b, + 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x54, + 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x28, 0x0a, 0x12, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x15, 0x0a, 0x13, 0x43, + 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x6d, 0x0a, 0x16, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, + 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, + 0x64, 0x65, 0x73, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x75, 0x70, + 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, + 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x64, 0x65, 0x73, + 0x63, 0x12, 0x14, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, + 0x00, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x22, 0x2d, 0x0a, 0x17, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, 0x75, + 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x22, 0x2d, 0x0a, 0x11, 0x41, 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, + 0x14, 0x0a, 0x12, 0x41, 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x32, 0x0a, 0x0f, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x56, + 0x69, 0x73, 0x69, 0x62, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x6e, 0x6f, 0x6e, 0x65, + 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x10, 0x02, 0x32, 0xbe, 0x03, 0x0a, 0x0b, 0x50, 0x6f, + 0x72, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x6a, 0x0a, 0x06, 0x54, 0x75, 0x6e, + 0x6e, 0x65, 0x6c, 0x12, 0x1d, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, + 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, + 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x76, 0x31, 0x2f, + 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2f, 0x7b, 0x70, 0x6f, 0x72, + 0x74, 0x7d, 0x3a, 0x01, 0x2a, 0x12, 0x6e, 0x0a, 0x0b, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x75, + 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x1e, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, + 0x72, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, + 0x72, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x2a, 0x16, 0x2f, + 0x76, 0x31, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2f, 0x7b, + 0x70, 0x6f, 0x72, 0x74, 0x7d, 0x12, 0x5e, 0x0a, 0x0f, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x22, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, + 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x54, + 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x73, + 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, + 0x69, 0x73, 0x68, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x73, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, + 0x6e, 0x65, 0x6c, 0x12, 0x1d, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, + 0x2e, 0x41, 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, + 0x41, 0x75, 0x74, 0x6f, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, + 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x2f, 0x61, 0x75, 0x74, 0x6f, + 0x2f, 0x7b, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x7d, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, + 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, + 0x69, 0x73, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_port_proto_rawDescOnce sync.Once + file_port_proto_rawDescData = file_port_proto_rawDesc +) + +func file_port_proto_rawDescGZIP() []byte { + file_port_proto_rawDescOnce.Do(func() { + file_port_proto_rawDescData = protoimpl.X.CompressGZIP(file_port_proto_rawDescData) + }) + return file_port_proto_rawDescData +} + +var file_port_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_port_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_port_proto_goTypes = []interface{}{ + (TunnelVisiblity)(0), // 0: supervisor.TunnelVisiblity + (*TunnelPortRequest)(nil), // 1: supervisor.TunnelPortRequest + (*TunnelPortResponse)(nil), // 2: supervisor.TunnelPortResponse + (*CloseTunnelRequest)(nil), // 3: supervisor.CloseTunnelRequest + (*CloseTunnelResponse)(nil), // 4: supervisor.CloseTunnelResponse + (*EstablishTunnelRequest)(nil), // 5: supervisor.EstablishTunnelRequest + (*EstablishTunnelResponse)(nil), // 6: supervisor.EstablishTunnelResponse + (*AutoTunnelRequest)(nil), // 7: supervisor.AutoTunnelRequest + (*AutoTunnelResponse)(nil), // 8: supervisor.AutoTunnelResponse +} +var file_port_proto_depIdxs = []int32{ + 0, // 0: supervisor.TunnelPortRequest.visibility:type_name -> supervisor.TunnelVisiblity + 1, // 1: supervisor.EstablishTunnelRequest.desc:type_name -> supervisor.TunnelPortRequest + 1, // 2: supervisor.PortService.Tunnel:input_type -> supervisor.TunnelPortRequest + 3, // 3: supervisor.PortService.CloseTunnel:input_type -> supervisor.CloseTunnelRequest + 5, // 4: supervisor.PortService.EstablishTunnel:input_type -> supervisor.EstablishTunnelRequest + 7, // 5: supervisor.PortService.AutoTunnel:input_type -> supervisor.AutoTunnelRequest + 2, // 6: supervisor.PortService.Tunnel:output_type -> supervisor.TunnelPortResponse + 4, // 7: supervisor.PortService.CloseTunnel:output_type -> supervisor.CloseTunnelResponse + 6, // 8: supervisor.PortService.EstablishTunnel:output_type -> supervisor.EstablishTunnelResponse + 8, // 9: supervisor.PortService.AutoTunnel:output_type -> supervisor.AutoTunnelResponse + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_port_proto_init() } +func file_port_proto_init() { + if File_port_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_port_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TunnelPortRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_port_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TunnelPortResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_port_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseTunnelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_port_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CloseTunnelResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_port_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EstablishTunnelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_port_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EstablishTunnelResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_port_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AutoTunnelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_port_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AutoTunnelResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_port_proto_msgTypes[4].OneofWrappers = []interface{}{ + (*EstablishTunnelRequest_Desc)(nil), + (*EstablishTunnelRequest_Data)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_port_proto_rawDesc, + NumEnums: 1, + NumMessages: 8, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_port_proto_goTypes, + DependencyIndexes: file_port_proto_depIdxs, + EnumInfos: file_port_proto_enumTypes, + MessageInfos: file_port_proto_msgTypes, + }.Build() + File_port_proto = out.File + file_port_proto_rawDesc = nil + file_port_proto_goTypes = nil + file_port_proto_depIdxs = nil +} diff --git a/components/supervisor-api/go/port.pb.gw.go b/components/supervisor-api/go/port.pb.gw.go new file mode 100644 index 00000000000000..7595b38b7f9f98 --- /dev/null +++ b/components/supervisor-api/go/port.pb.gw.go @@ -0,0 +1,403 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: port.proto + +/* +Package api is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package api + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_PortService_Tunnel_0(ctx context.Context, marshaler runtime.Marshaler, client PortServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq TunnelPortRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["port"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port") + } + + protoReq.Port, err = runtime.Uint32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port", err) + } + + msg, err := client.Tunnel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_PortService_Tunnel_0(ctx context.Context, marshaler runtime.Marshaler, server PortServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq TunnelPortRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["port"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port") + } + + protoReq.Port, err = runtime.Uint32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port", err) + } + + msg, err := server.Tunnel(ctx, &protoReq) + return msg, metadata, err + +} + +func request_PortService_CloseTunnel_0(ctx context.Context, marshaler runtime.Marshaler, client PortServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CloseTunnelRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["port"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port") + } + + protoReq.Port, err = runtime.Uint32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port", err) + } + + msg, err := client.CloseTunnel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_PortService_CloseTunnel_0(ctx context.Context, marshaler runtime.Marshaler, server PortServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CloseTunnelRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["port"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "port") + } + + protoReq.Port, err = runtime.Uint32(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "port", err) + } + + msg, err := server.CloseTunnel(ctx, &protoReq) + return msg, metadata, err + +} + +func request_PortService_AutoTunnel_0(ctx context.Context, marshaler runtime.Marshaler, client PortServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AutoTunnelRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["enabled"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "enabled") + } + + protoReq.Enabled, err = runtime.Bool(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "enabled", err) + } + + msg, err := client.AutoTunnel(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_PortService_AutoTunnel_0(ctx context.Context, marshaler runtime.Marshaler, server PortServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AutoTunnelRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["enabled"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "enabled") + } + + protoReq.Enabled, err = runtime.Bool(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "enabled", err) + } + + msg, err := server.AutoTunnel(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterPortServiceHandlerServer registers the http handlers for service PortService to "mux". +// UnaryRPC :call PortServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterPortServiceHandlerFromEndpoint instead. +func RegisterPortServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server PortServiceServer) error { + + mux.Handle("POST", pattern_PortService_Tunnel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/supervisor.PortService/Tunnel") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_PortService_Tunnel_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_PortService_Tunnel_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_PortService_CloseTunnel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/supervisor.PortService/CloseTunnel") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_PortService_CloseTunnel_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_PortService_CloseTunnel_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_PortService_AutoTunnel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/supervisor.PortService/AutoTunnel") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_PortService_AutoTunnel_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_PortService_AutoTunnel_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterPortServiceHandlerFromEndpoint is same as RegisterPortServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterPortServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterPortServiceHandler(ctx, mux, conn) +} + +// RegisterPortServiceHandler registers the http handlers for service PortService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterPortServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterPortServiceHandlerClient(ctx, mux, NewPortServiceClient(conn)) +} + +// RegisterPortServiceHandlerClient registers the http handlers for service PortService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "PortServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "PortServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "PortServiceClient" to call the correct interceptors. +func RegisterPortServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client PortServiceClient) error { + + mux.Handle("POST", pattern_PortService_Tunnel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/supervisor.PortService/Tunnel") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_PortService_Tunnel_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_PortService_Tunnel_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_PortService_CloseTunnel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/supervisor.PortService/CloseTunnel") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_PortService_CloseTunnel_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_PortService_CloseTunnel_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_PortService_AutoTunnel_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/supervisor.PortService/AutoTunnel") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_PortService_AutoTunnel_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_PortService_AutoTunnel_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_PortService_Tunnel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 1}, []string{"v1", "port", "tunnel"}, "")) + + pattern_PortService_CloseTunnel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 1}, []string{"v1", "port", "tunnel"}, "")) + + pattern_PortService_AutoTunnel_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"v1", "port", "tunnel", "auto", "enabled"}, "")) +) + +var ( + forward_PortService_Tunnel_0 = runtime.ForwardResponseMessage + + forward_PortService_CloseTunnel_0 = runtime.ForwardResponseMessage + + forward_PortService_AutoTunnel_0 = runtime.ForwardResponseMessage +) diff --git a/components/supervisor-api/go/port_grpc.pb.go b/components/supervisor-api/go/port_grpc.pb.go new file mode 100644 index 00000000000000..9e746f120ee124 --- /dev/null +++ b/components/supervisor-api/go/port_grpc.pb.go @@ -0,0 +1,256 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package api + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// PortServiceClient is the client API for PortService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PortServiceClient interface { + // Tunnel notifies clients to install listeners on remote machines. + // After that such clients should call EstablishTunnel to forward incoming connections. + Tunnel(ctx context.Context, in *TunnelPortRequest, opts ...grpc.CallOption) (*TunnelPortResponse, error) + // CloseTunnel notifies clients to remove listeners on remote machines. + CloseTunnel(ctx context.Context, in *CloseTunnelRequest, opts ...grpc.CallOption) (*CloseTunnelResponse, error) + // EstablishTunnel actually establishes the tunnel for an incoming connection on a remote machine. + EstablishTunnel(ctx context.Context, opts ...grpc.CallOption) (PortService_EstablishTunnelClient, error) + // AutoTunnel controls enablement of auto tunneling + AutoTunnel(ctx context.Context, in *AutoTunnelRequest, opts ...grpc.CallOption) (*AutoTunnelResponse, error) +} + +type portServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPortServiceClient(cc grpc.ClientConnInterface) PortServiceClient { + return &portServiceClient{cc} +} + +func (c *portServiceClient) Tunnel(ctx context.Context, in *TunnelPortRequest, opts ...grpc.CallOption) (*TunnelPortResponse, error) { + out := new(TunnelPortResponse) + err := c.cc.Invoke(ctx, "/supervisor.PortService/Tunnel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *portServiceClient) CloseTunnel(ctx context.Context, in *CloseTunnelRequest, opts ...grpc.CallOption) (*CloseTunnelResponse, error) { + out := new(CloseTunnelResponse) + err := c.cc.Invoke(ctx, "/supervisor.PortService/CloseTunnel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *portServiceClient) EstablishTunnel(ctx context.Context, opts ...grpc.CallOption) (PortService_EstablishTunnelClient, error) { + stream, err := c.cc.NewStream(ctx, &PortService_ServiceDesc.Streams[0], "/supervisor.PortService/EstablishTunnel", opts...) + if err != nil { + return nil, err + } + x := &portServiceEstablishTunnelClient{stream} + return x, nil +} + +type PortService_EstablishTunnelClient interface { + Send(*EstablishTunnelRequest) error + Recv() (*EstablishTunnelResponse, error) + grpc.ClientStream +} + +type portServiceEstablishTunnelClient struct { + grpc.ClientStream +} + +func (x *portServiceEstablishTunnelClient) Send(m *EstablishTunnelRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *portServiceEstablishTunnelClient) Recv() (*EstablishTunnelResponse, error) { + m := new(EstablishTunnelResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *portServiceClient) AutoTunnel(ctx context.Context, in *AutoTunnelRequest, opts ...grpc.CallOption) (*AutoTunnelResponse, error) { + out := new(AutoTunnelResponse) + err := c.cc.Invoke(ctx, "/supervisor.PortService/AutoTunnel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PortServiceServer is the server API for PortService service. +// All implementations must embed UnimplementedPortServiceServer +// for forward compatibility +type PortServiceServer interface { + // Tunnel notifies clients to install listeners on remote machines. + // After that such clients should call EstablishTunnel to forward incoming connections. + Tunnel(context.Context, *TunnelPortRequest) (*TunnelPortResponse, error) + // CloseTunnel notifies clients to remove listeners on remote machines. + CloseTunnel(context.Context, *CloseTunnelRequest) (*CloseTunnelResponse, error) + // EstablishTunnel actually establishes the tunnel for an incoming connection on a remote machine. + EstablishTunnel(PortService_EstablishTunnelServer) error + // AutoTunnel controls enablement of auto tunneling + AutoTunnel(context.Context, *AutoTunnelRequest) (*AutoTunnelResponse, error) + mustEmbedUnimplementedPortServiceServer() +} + +// UnimplementedPortServiceServer must be embedded to have forward compatible implementations. +type UnimplementedPortServiceServer struct { +} + +func (UnimplementedPortServiceServer) Tunnel(context.Context, *TunnelPortRequest) (*TunnelPortResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Tunnel not implemented") +} +func (UnimplementedPortServiceServer) CloseTunnel(context.Context, *CloseTunnelRequest) (*CloseTunnelResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CloseTunnel not implemented") +} +func (UnimplementedPortServiceServer) EstablishTunnel(PortService_EstablishTunnelServer) error { + return status.Errorf(codes.Unimplemented, "method EstablishTunnel not implemented") +} +func (UnimplementedPortServiceServer) AutoTunnel(context.Context, *AutoTunnelRequest) (*AutoTunnelResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AutoTunnel not implemented") +} +func (UnimplementedPortServiceServer) mustEmbedUnimplementedPortServiceServer() {} + +// UnsafePortServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PortServiceServer will +// result in compilation errors. +type UnsafePortServiceServer interface { + mustEmbedUnimplementedPortServiceServer() +} + +func RegisterPortServiceServer(s grpc.ServiceRegistrar, srv PortServiceServer) { + s.RegisterService(&PortService_ServiceDesc, srv) +} + +func _PortService_Tunnel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TunnelPortRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PortServiceServer).Tunnel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/supervisor.PortService/Tunnel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PortServiceServer).Tunnel(ctx, req.(*TunnelPortRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PortService_CloseTunnel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CloseTunnelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PortServiceServer).CloseTunnel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/supervisor.PortService/CloseTunnel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PortServiceServer).CloseTunnel(ctx, req.(*CloseTunnelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PortService_EstablishTunnel_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(PortServiceServer).EstablishTunnel(&portServiceEstablishTunnelServer{stream}) +} + +type PortService_EstablishTunnelServer interface { + Send(*EstablishTunnelResponse) error + Recv() (*EstablishTunnelRequest, error) + grpc.ServerStream +} + +type portServiceEstablishTunnelServer struct { + grpc.ServerStream +} + +func (x *portServiceEstablishTunnelServer) Send(m *EstablishTunnelResponse) error { + return x.ServerStream.SendMsg(m) +} + +func (x *portServiceEstablishTunnelServer) Recv() (*EstablishTunnelRequest, error) { + m := new(EstablishTunnelRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _PortService_AutoTunnel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AutoTunnelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PortServiceServer).AutoTunnel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/supervisor.PortService/AutoTunnel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PortServiceServer).AutoTunnel(ctx, req.(*AutoTunnelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// PortService_ServiceDesc is the grpc.ServiceDesc for PortService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PortService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "supervisor.PortService", + HandlerType: (*PortServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Tunnel", + Handler: _PortService_Tunnel_Handler, + }, + { + MethodName: "CloseTunnel", + Handler: _PortService_CloseTunnel_Handler, + }, + { + MethodName: "AutoTunnel", + Handler: _PortService_AutoTunnel_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "EstablishTunnel", + Handler: _PortService_EstablishTunnel_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "port.proto", +} diff --git a/components/supervisor-api/go/status.pb.go b/components/supervisor-api/go/status.pb.go index 890b520436200e..6c8b1a0204ba94 100644 --- a/components/supervisor-api/go/status.pb.go +++ b/components/supervisor-api/go/status.pb.go @@ -758,6 +758,73 @@ func (x *ExposedPortInfo) GetOnExposed() OnPortExposedAction { return OnPortExposedAction_ignore } +type TunneledPortInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // target port is the desired port on the remote machine + TargetPort uint32 `protobuf:"varint,1,opt,name=target_port,json=targetPort,proto3" json:"target_port,omitempty"` + // visibility determines if the listener on remote machine should accept connections from localhost or network + // visibility none means that the port should not be tunneled + Visibility TunnelVisiblity `protobuf:"varint,2,opt,name=visibility,proto3,enum=supervisor.TunnelVisiblity" json:"visibility,omitempty"` + // map of remote clients indicates on which remote port each client is listening to + Clients map[string]uint32 `protobuf:"bytes,3,rep,name=clients,proto3" json:"clients,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` +} + +func (x *TunneledPortInfo) Reset() { + *x = TunneledPortInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_status_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TunneledPortInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TunneledPortInfo) ProtoMessage() {} + +func (x *TunneledPortInfo) ProtoReflect() protoreflect.Message { + mi := &file_status_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TunneledPortInfo.ProtoReflect.Descriptor instead. +func (*TunneledPortInfo) Descriptor() ([]byte, []int) { + return file_status_proto_rawDescGZIP(), []int{11} +} + +func (x *TunneledPortInfo) GetTargetPort() uint32 { + if x != nil { + return x.TargetPort + } + return 0 +} + +func (x *TunneledPortInfo) GetVisibility() TunnelVisiblity { + if x != nil { + return x.Visibility + } + return TunnelVisiblity_none +} + +func (x *TunneledPortInfo) GetClients() map[string]uint32 { + if x != nil { + return x.Clients + } + return nil +} + type PortsStatus struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -775,12 +842,15 @@ type PortsStatus struct { // Exposed provides information when a port is exposed. If this field isn't set, // the port is not available from outside the workspace (i.e. the internet). Exposed *ExposedPortInfo `protobuf:"bytes,5,opt,name=exposed,proto3" json:"exposed,omitempty"` + // Tunneled provides information when a port is tunneled. If not present then + // the port is not tunneled. + Tunneled *TunneledPortInfo `protobuf:"bytes,6,opt,name=tunneled,proto3" json:"tunneled,omitempty"` } func (x *PortsStatus) Reset() { *x = PortsStatus{} if protoimpl.UnsafeEnabled { - mi := &file_status_proto_msgTypes[11] + mi := &file_status_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -793,7 +863,7 @@ func (x *PortsStatus) String() string { func (*PortsStatus) ProtoMessage() {} func (x *PortsStatus) ProtoReflect() protoreflect.Message { - mi := &file_status_proto_msgTypes[11] + mi := &file_status_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -806,7 +876,7 @@ func (x *PortsStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use PortsStatus.ProtoReflect.Descriptor instead. func (*PortsStatus) Descriptor() ([]byte, []int) { - return file_status_proto_rawDescGZIP(), []int{11} + return file_status_proto_rawDescGZIP(), []int{12} } func (x *PortsStatus) GetLocalPort() uint32 { @@ -837,6 +907,13 @@ func (x *PortsStatus) GetExposed() *ExposedPortInfo { return nil } +func (x *PortsStatus) GetTunneled() *TunneledPortInfo { + if x != nil { + return x.Tunneled + } + return nil +} + type TasksStatusRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -850,7 +927,7 @@ type TasksStatusRequest struct { func (x *TasksStatusRequest) Reset() { *x = TasksStatusRequest{} if protoimpl.UnsafeEnabled { - mi := &file_status_proto_msgTypes[12] + mi := &file_status_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -863,7 +940,7 @@ func (x *TasksStatusRequest) String() string { func (*TasksStatusRequest) ProtoMessage() {} func (x *TasksStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_status_proto_msgTypes[12] + mi := &file_status_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -876,7 +953,7 @@ func (x *TasksStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TasksStatusRequest.ProtoReflect.Descriptor instead. func (*TasksStatusRequest) Descriptor() ([]byte, []int) { - return file_status_proto_rawDescGZIP(), []int{12} + return file_status_proto_rawDescGZIP(), []int{13} } func (x *TasksStatusRequest) GetObserve() bool { @@ -897,7 +974,7 @@ type TasksStatusResponse struct { func (x *TasksStatusResponse) Reset() { *x = TasksStatusResponse{} if protoimpl.UnsafeEnabled { - mi := &file_status_proto_msgTypes[13] + mi := &file_status_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -910,7 +987,7 @@ func (x *TasksStatusResponse) String() string { func (*TasksStatusResponse) ProtoMessage() {} func (x *TasksStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_status_proto_msgTypes[13] + mi := &file_status_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -923,7 +1000,7 @@ func (x *TasksStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TasksStatusResponse.ProtoReflect.Descriptor instead. func (*TasksStatusResponse) Descriptor() ([]byte, []int) { - return file_status_proto_rawDescGZIP(), []int{13} + return file_status_proto_rawDescGZIP(), []int{14} } func (x *TasksStatusResponse) GetTasks() []*TaskStatus { @@ -947,7 +1024,7 @@ type TaskStatus struct { func (x *TaskStatus) Reset() { *x = TaskStatus{} if protoimpl.UnsafeEnabled { - mi := &file_status_proto_msgTypes[14] + mi := &file_status_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -960,7 +1037,7 @@ func (x *TaskStatus) String() string { func (*TaskStatus) ProtoMessage() {} func (x *TaskStatus) ProtoReflect() protoreflect.Message { - mi := &file_status_proto_msgTypes[14] + mi := &file_status_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -973,7 +1050,7 @@ func (x *TaskStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskStatus.ProtoReflect.Descriptor instead. func (*TaskStatus) Descriptor() ([]byte, []int) { - return file_status_proto_rawDescGZIP(), []int{14} + return file_status_proto_rawDescGZIP(), []int{15} } func (x *TaskStatus) GetId() string { @@ -1017,7 +1094,7 @@ type TaskPresentation struct { func (x *TaskPresentation) Reset() { *x = TaskPresentation{} if protoimpl.UnsafeEnabled { - mi := &file_status_proto_msgTypes[15] + mi := &file_status_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1030,7 +1107,7 @@ func (x *TaskPresentation) String() string { func (*TaskPresentation) ProtoMessage() {} func (x *TaskPresentation) ProtoReflect() protoreflect.Message { - mi := &file_status_proto_msgTypes[15] + mi := &file_status_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1043,7 +1120,7 @@ func (x *TaskPresentation) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskPresentation.ProtoReflect.Descriptor instead. func (*TaskPresentation) Descriptor() ([]byte, []int) { - return file_status_proto_rawDescGZIP(), []int{15} + return file_status_proto_rawDescGZIP(), []int{16} } func (x *TaskPresentation) GetName() string { @@ -1073,49 +1150,65 @@ var file_status_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x19, 0x0a, 0x17, 0x53, 0x75, 0x70, 0x65, - 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x22, 0x2a, 0x0a, 0x18, 0x53, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, - 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, - 0x26, 0x0a, 0x10, 0x49, 0x44, 0x45, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x61, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x04, 0x77, 0x61, 0x69, 0x74, 0x22, 0x23, 0x0a, 0x11, 0x49, 0x44, 0x45, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, - 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x2a, 0x0a, 0x14, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x61, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x04, 0x77, 0x61, 0x69, 0x74, 0x22, 0x68, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, - 0x31, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x19, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x43, 0x6f, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x22, 0x15, 0x0a, 0x13, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x41, 0x0a, 0x14, 0x42, 0x61, 0x63, - 0x6b, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x76, 0x61, 0x69, - 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x63, 0x61, 0x6e, - 0x61, 0x72, 0x79, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x2e, 0x0a, 0x12, - 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x22, 0x44, 0x0a, 0x13, - 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, - 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x70, 0x6f, 0x72, - 0x74, 0x73, 0x22, 0x9f, 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x50, 0x6f, - 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3a, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, - 0x6c, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x73, 0x75, 0x70, - 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x56, 0x69, 0x73, 0x69, - 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, - 0x74, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x75, 0x72, 0x6c, 0x12, 0x3e, 0x0a, 0x0a, 0x6f, 0x6e, 0x5f, 0x65, 0x78, 0x70, 0x6f, 0x73, - 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, - 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x4f, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x45, 0x78, 0x70, 0x6f, - 0x73, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x6e, 0x45, 0x78, 0x70, - 0x6f, 0x73, 0x65, 0x64, 0x22, 0x9c, 0x01, 0x0a, 0x0b, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x19, 0x0a, 0x17, 0x53, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, + 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x2a, 0x0a, 0x18, 0x53, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, + 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x26, 0x0a, 0x10, 0x49, + 0x44, 0x45, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x12, 0x0a, 0x04, 0x77, 0x61, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x77, + 0x61, 0x69, 0x74, 0x22, 0x23, 0x0a, 0x11, 0x49, 0x44, 0x45, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x6b, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x6f, 0x6b, 0x22, 0x2a, 0x0a, 0x14, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x77, 0x61, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, + 0x77, 0x61, 0x69, 0x74, 0x22, 0x68, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x73, 0x75, + 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x15, + 0x0a, 0x13, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x41, 0x0a, 0x14, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x63, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x63, 0x61, 0x6e, 0x61, 0x72, 0x79, 0x41, + 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x2e, 0x0a, 0x12, 0x50, 0x6f, 0x72, 0x74, + 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x22, 0x44, 0x0a, 0x13, 0x50, 0x6f, 0x72, 0x74, + 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x2d, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x50, 0x6f, 0x72, 0x74, + 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x22, 0x9f, + 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x3a, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, + 0x73, 0x6f, 0x72, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, + 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x12, 0x3e, 0x0a, 0x0a, 0x6f, 0x6e, 0x5f, 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, + 0x72, 0x2e, 0x4f, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x6e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, + 0x22, 0xf1, 0x01, 0x0a, 0x10, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x65, 0x64, 0x50, 0x6f, 0x72, + 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, + 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, + 0x6c, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x73, 0x75, 0x70, + 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x56, 0x69, + 0x73, 0x69, 0x62, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0a, 0x76, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, + 0x69, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, + 0x72, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd6, 0x01, 0x0a, 0x0b, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x70, 0x6f, @@ -1125,103 +1218,107 @@ var file_status_proto_rawDesc = []byte{ 0x65, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x65, 0x78, 0x70, 0x6f, - 0x73, 0x65, 0x64, 0x22, 0x2e, 0x0a, 0x12, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x62, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x62, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x22, 0x43, 0x0a, 0x13, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x74, 0x61, - 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x75, 0x70, 0x65, - 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x0a, 0x54, 0x61, 0x73, - 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, - 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, - 0x12, 0x40, 0x0a, 0x0c, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, - 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0x5c, 0x0a, 0x10, 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6f, 0x70, - 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x70, 0x65, - 0x6e, 0x49, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x6e, 0x4d, 0x6f, 0x64, 0x65, - 0x2a, 0x43, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x10, - 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x70, 0x72, 0x65, 0x62, 0x75, - 0x69, 0x6c, 0x64, 0x10, 0x02, 0x2a, 0x29, 0x0a, 0x0e, 0x50, 0x6f, 0x72, 0x74, 0x56, 0x69, 0x73, - 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, - 0x74, 0x65, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x10, 0x01, - 0x2a, 0x65, 0x0a, 0x13, 0x4f, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, - 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x69, 0x67, 0x6e, 0x6f, 0x72, - 0x65, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x72, 0x6f, 0x77, - 0x73, 0x65, 0x72, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x70, 0x72, - 0x65, 0x76, 0x69, 0x65, 0x77, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x79, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x72, - 0x69, 0x76, 0x61, 0x74, 0x65, 0x10, 0x04, 0x2a, 0x31, 0x0a, 0x09, 0x54, 0x61, 0x73, 0x6b, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x10, - 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x0a, - 0x0a, 0x06, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x10, 0x02, 0x32, 0xcb, 0x06, 0x0a, 0x0d, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7c, 0x0a, 0x10, - 0x53, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x23, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x53, 0x75, - 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, - 0x6f, 0x72, 0x2e, 0x53, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, - 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x12, 0x83, 0x01, 0x0a, 0x09, 0x49, - 0x44, 0x45, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1c, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, - 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x49, 0x44, 0x45, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, - 0x73, 0x6f, 0x72, 0x2e, 0x49, 0x44, 0x45, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x33, 0x12, 0x0e, 0x2f, - 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x69, 0x64, 0x65, 0x5a, 0x21, 0x12, - 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x69, 0x64, 0x65, 0x2f, - 0x77, 0x61, 0x69, 0x74, 0x2f, 0x7b, 0x77, 0x61, 0x69, 0x74, 0x3d, 0x74, 0x72, 0x75, 0x65, 0x7d, - 0x12, 0x97, 0x01, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x20, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, - 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, - 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x41, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3b, 0x12, - 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x5a, 0x25, 0x12, 0x23, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2f, 0x77, 0x61, 0x69, 0x74, 0x2f, 0x7b, - 0x77, 0x61, 0x69, 0x74, 0x3d, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x12, 0x6c, 0x0a, 0x0c, 0x42, 0x61, - 0x63, 0x6b, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x2e, 0x73, 0x75, 0x70, - 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x73, 0x75, - 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x95, 0x01, 0x0a, 0x0b, 0x50, 0x6f, 0x72, - 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, - 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, - 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x3d, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x70, 0x6f, - 0x72, 0x74, 0x73, 0x5a, 0x29, 0x12, 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x2f, - 0x7b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x3d, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x30, 0x01, - 0x12, 0x95, 0x01, 0x0a, 0x0b, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x1e, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, - 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, - 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x43, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3d, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5a, 0x29, 0x12, 0x27, 0x2f, - 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2f, - 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x2f, 0x7b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x3d, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x30, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, - 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, - 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x08, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x65, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, + 0x6f, 0x72, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x65, 0x64, 0x22, 0x2e, 0x0a, + 0x12, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x22, 0x43, 0x0a, + 0x13, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, + 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x05, 0x74, 0x61, 0x73, + 0x6b, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x2b, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x15, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, + 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1a, + 0x0a, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x40, 0x0a, 0x0c, 0x70, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, + 0x73, 0x6b, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0c, + 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x5c, 0x0a, 0x10, + 0x54, 0x61, 0x73, 0x6b, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x69, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x49, 0x6e, 0x12, 0x1b, 0x0a, + 0x09, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x6f, 0x70, 0x65, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x2a, 0x43, 0x0a, 0x0d, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x66, + 0x72, 0x6f, 0x6d, 0x5f, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x66, + 0x72, 0x6f, 0x6d, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, + 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x10, 0x02, 0x2a, + 0x29, 0x0a, 0x0e, 0x50, 0x6f, 0x72, 0x74, 0x56, 0x69, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, + 0x79, 0x12, 0x0b, 0x0a, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x10, 0x00, 0x12, 0x0a, + 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x10, 0x01, 0x2a, 0x65, 0x0a, 0x13, 0x4f, 0x6e, + 0x50, 0x6f, 0x72, 0x74, 0x45, 0x78, 0x70, 0x6f, 0x73, 0x65, 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x10, 0x00, 0x12, 0x10, 0x0a, + 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x10, 0x01, 0x12, + 0x10, 0x0a, 0x0c, 0x6f, 0x70, 0x65, 0x6e, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x65, 0x77, 0x10, + 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x10, 0x03, 0x12, 0x12, 0x0a, + 0x0e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x10, + 0x04, 0x2a, 0x31, 0x0a, 0x09, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0b, + 0x0a, 0x07, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x72, + 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0x64, 0x10, 0x02, 0x32, 0xcb, 0x06, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x7c, 0x0a, 0x10, 0x53, 0x75, 0x70, 0x65, 0x72, 0x76, + 0x69, 0x73, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x23, 0x2e, 0x73, 0x75, 0x70, + 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x53, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, + 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x24, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x53, 0x75, 0x70, + 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, + 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, + 0x69, 0x73, 0x6f, 0x72, 0x12, 0x83, 0x01, 0x0a, 0x09, 0x49, 0x44, 0x45, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1c, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, + 0x49, 0x44, 0x45, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x49, 0x44, + 0x45, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x39, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x33, 0x12, 0x0e, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x2f, 0x69, 0x64, 0x65, 0x5a, 0x21, 0x12, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x69, 0x64, 0x65, 0x2f, 0x77, 0x61, 0x69, 0x74, 0x2f, 0x7b, + 0x77, 0x61, 0x69, 0x74, 0x3d, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x12, 0x97, 0x01, 0x0a, 0x0d, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x2e, 0x73, + 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, + 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x41, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3b, 0x12, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5a, 0x25, 0x12, + 0x23, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x2f, 0x77, 0x61, 0x69, 0x74, 0x2f, 0x7b, 0x77, 0x61, 0x69, 0x74, 0x3d, 0x74, + 0x72, 0x75, 0x65, 0x7d, 0x12, 0x6c, 0x0a, 0x0c, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, + 0x72, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, + 0x6f, 0x72, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, + 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x62, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x12, 0x95, 0x01, 0x0a, 0x0b, 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x1e, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, + 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, + 0x50, 0x6f, 0x72, 0x74, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x43, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3d, 0x12, 0x10, 0x2f, 0x76, 0x31, + 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x5a, 0x29, 0x12, + 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x2f, 0x7b, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x3d, 0x74, 0x72, 0x75, 0x65, 0x7d, 0x30, 0x01, 0x12, 0x95, 0x01, 0x0a, 0x0b, 0x54, + 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x2e, 0x73, 0x75, 0x70, + 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x73, 0x75, 0x70, + 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x43, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x3d, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2f, + 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5a, 0x29, 0x12, 0x27, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2f, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x2f, 0x7b, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x3d, 0x74, 0x72, 0x75, 0x65, 0x7d, + 0x30, 0x01, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, + 0x64, 0x2f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1237,7 +1334,7 @@ func file_status_proto_rawDescGZIP() []byte { } var file_status_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_status_proto_msgTypes = make([]protoimpl.MessageInfo, 16) +var file_status_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_status_proto_goTypes = []interface{}{ (ContentSource)(0), // 0: supervisor.ContentSource (PortVisibility)(0), // 1: supervisor.PortVisibility @@ -1254,38 +1351,44 @@ var file_status_proto_goTypes = []interface{}{ (*PortsStatusRequest)(nil), // 12: supervisor.PortsStatusRequest (*PortsStatusResponse)(nil), // 13: supervisor.PortsStatusResponse (*ExposedPortInfo)(nil), // 14: supervisor.ExposedPortInfo - (*PortsStatus)(nil), // 15: supervisor.PortsStatus - (*TasksStatusRequest)(nil), // 16: supervisor.TasksStatusRequest - (*TasksStatusResponse)(nil), // 17: supervisor.TasksStatusResponse - (*TaskStatus)(nil), // 18: supervisor.TaskStatus - (*TaskPresentation)(nil), // 19: supervisor.TaskPresentation + (*TunneledPortInfo)(nil), // 15: supervisor.TunneledPortInfo + (*PortsStatus)(nil), // 16: supervisor.PortsStatus + (*TasksStatusRequest)(nil), // 17: supervisor.TasksStatusRequest + (*TasksStatusResponse)(nil), // 18: supervisor.TasksStatusResponse + (*TaskStatus)(nil), // 19: supervisor.TaskStatus + (*TaskPresentation)(nil), // 20: supervisor.TaskPresentation + nil, // 21: supervisor.TunneledPortInfo.ClientsEntry + (TunnelVisiblity)(0), // 22: supervisor.TunnelVisiblity } var file_status_proto_depIdxs = []int32{ 0, // 0: supervisor.ContentStatusResponse.source:type_name -> supervisor.ContentSource - 15, // 1: supervisor.PortsStatusResponse.ports:type_name -> supervisor.PortsStatus + 16, // 1: supervisor.PortsStatusResponse.ports:type_name -> supervisor.PortsStatus 1, // 2: supervisor.ExposedPortInfo.visibility:type_name -> supervisor.PortVisibility 2, // 3: supervisor.ExposedPortInfo.on_exposed:type_name -> supervisor.OnPortExposedAction - 14, // 4: supervisor.PortsStatus.exposed:type_name -> supervisor.ExposedPortInfo - 18, // 5: supervisor.TasksStatusResponse.tasks:type_name -> supervisor.TaskStatus - 3, // 6: supervisor.TaskStatus.state:type_name -> supervisor.TaskState - 19, // 7: supervisor.TaskStatus.presentation:type_name -> supervisor.TaskPresentation - 4, // 8: supervisor.StatusService.SupervisorStatus:input_type -> supervisor.SupervisorStatusRequest - 6, // 9: supervisor.StatusService.IDEStatus:input_type -> supervisor.IDEStatusRequest - 8, // 10: supervisor.StatusService.ContentStatus:input_type -> supervisor.ContentStatusRequest - 10, // 11: supervisor.StatusService.BackupStatus:input_type -> supervisor.BackupStatusRequest - 12, // 12: supervisor.StatusService.PortsStatus:input_type -> supervisor.PortsStatusRequest - 16, // 13: supervisor.StatusService.TasksStatus:input_type -> supervisor.TasksStatusRequest - 5, // 14: supervisor.StatusService.SupervisorStatus:output_type -> supervisor.SupervisorStatusResponse - 7, // 15: supervisor.StatusService.IDEStatus:output_type -> supervisor.IDEStatusResponse - 9, // 16: supervisor.StatusService.ContentStatus:output_type -> supervisor.ContentStatusResponse - 11, // 17: supervisor.StatusService.BackupStatus:output_type -> supervisor.BackupStatusResponse - 13, // 18: supervisor.StatusService.PortsStatus:output_type -> supervisor.PortsStatusResponse - 17, // 19: supervisor.StatusService.TasksStatus:output_type -> supervisor.TasksStatusResponse - 14, // [14:20] is the sub-list for method output_type - 8, // [8:14] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 22, // 4: supervisor.TunneledPortInfo.visibility:type_name -> supervisor.TunnelVisiblity + 21, // 5: supervisor.TunneledPortInfo.clients:type_name -> supervisor.TunneledPortInfo.ClientsEntry + 14, // 6: supervisor.PortsStatus.exposed:type_name -> supervisor.ExposedPortInfo + 15, // 7: supervisor.PortsStatus.tunneled:type_name -> supervisor.TunneledPortInfo + 19, // 8: supervisor.TasksStatusResponse.tasks:type_name -> supervisor.TaskStatus + 3, // 9: supervisor.TaskStatus.state:type_name -> supervisor.TaskState + 20, // 10: supervisor.TaskStatus.presentation:type_name -> supervisor.TaskPresentation + 4, // 11: supervisor.StatusService.SupervisorStatus:input_type -> supervisor.SupervisorStatusRequest + 6, // 12: supervisor.StatusService.IDEStatus:input_type -> supervisor.IDEStatusRequest + 8, // 13: supervisor.StatusService.ContentStatus:input_type -> supervisor.ContentStatusRequest + 10, // 14: supervisor.StatusService.BackupStatus:input_type -> supervisor.BackupStatusRequest + 12, // 15: supervisor.StatusService.PortsStatus:input_type -> supervisor.PortsStatusRequest + 17, // 16: supervisor.StatusService.TasksStatus:input_type -> supervisor.TasksStatusRequest + 5, // 17: supervisor.StatusService.SupervisorStatus:output_type -> supervisor.SupervisorStatusResponse + 7, // 18: supervisor.StatusService.IDEStatus:output_type -> supervisor.IDEStatusResponse + 9, // 19: supervisor.StatusService.ContentStatus:output_type -> supervisor.ContentStatusResponse + 11, // 20: supervisor.StatusService.BackupStatus:output_type -> supervisor.BackupStatusResponse + 13, // 21: supervisor.StatusService.PortsStatus:output_type -> supervisor.PortsStatusResponse + 18, // 22: supervisor.StatusService.TasksStatus:output_type -> supervisor.TasksStatusResponse + 17, // [17:23] is the sub-list for method output_type + 11, // [11:17] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_status_proto_init() } @@ -1293,6 +1396,7 @@ func file_status_proto_init() { if File_status_proto != nil { return } + file_port_proto_init() if !protoimpl.UnsafeEnabled { file_status_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SupervisorStatusRequest); i { @@ -1427,7 +1531,7 @@ func file_status_proto_init() { } } file_status_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PortsStatus); i { + switch v := v.(*TunneledPortInfo); i { case 0: return &v.state case 1: @@ -1439,7 +1543,7 @@ func file_status_proto_init() { } } file_status_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TasksStatusRequest); i { + switch v := v.(*PortsStatus); i { case 0: return &v.state case 1: @@ -1451,7 +1555,7 @@ func file_status_proto_init() { } } file_status_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TasksStatusResponse); i { + switch v := v.(*TasksStatusRequest); i { case 0: return &v.state case 1: @@ -1463,7 +1567,7 @@ func file_status_proto_init() { } } file_status_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TaskStatus); i { + switch v := v.(*TasksStatusResponse); i { case 0: return &v.state case 1: @@ -1475,6 +1579,18 @@ func file_status_proto_init() { } } file_status_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TaskStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_status_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TaskPresentation); i { case 0: return &v.state @@ -1493,7 +1609,7 @@ func file_status_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_status_proto_rawDesc, NumEnums: 4, - NumMessages: 16, + NumMessages: 18, NumExtensions: 0, NumServices: 1, }, diff --git a/components/supervisor-api/port.proto b/components/supervisor-api/port.proto new file mode 100644 index 00000000000000..e1b601d6ac73e4 --- /dev/null +++ b/components/supervisor-api/port.proto @@ -0,0 +1,67 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +syntax = "proto3"; + +package supervisor; + +import "google/api/annotations.proto"; + +option go_package = "github.com/gitpod-io/gitpod/supervisor/api"; + +service PortService { + // Tunnel notifies clients to install listeners on remote machines. + // After that such clients should call EstablishTunnel to forward incoming connections. + rpc Tunnel(TunnelPortRequest) returns (TunnelPortResponse) { + option (google.api.http) = { + post : "/v1/port/tunnel/{port}" + body : "*" + }; + } + + // CloseTunnel notifies clients to remove listeners on remote machines. + rpc CloseTunnel(CloseTunnelRequest) returns (CloseTunnelResponse) { + option (google.api.http) = { + delete : "/v1/port/tunnel/{port}" + }; + } + + // EstablishTunnel actually establishes the tunnel for an incoming connection on a remote machine. + rpc EstablishTunnel(stream EstablishTunnelRequest) + returns (stream EstablishTunnelResponse); + + // AutoTunnel controls enablement of auto tunneling + rpc AutoTunnel(AutoTunnelRequest) returns (AutoTunnelResponse) { + option (google.api.http) = { + post : "/v1/port/tunnel/auto/{enabled}" + }; + } +} +enum TunnelVisiblity { + none = 0; + host = 1; + network = 2; +} +message TunnelPortRequest { + uint32 port = 1; + uint32 target_port = 2; + TunnelVisiblity visibility = 3; + string client_id = 4; +} +message TunnelPortResponse {} + +message CloseTunnelRequest { uint32 port = 1; } +message CloseTunnelResponse {} + +message EstablishTunnelRequest { + oneof output { + TunnelPortRequest desc = 1; + bytes data = 2; + }; +} + +message EstablishTunnelResponse { bytes data = 1; } + +message AutoTunnelRequest { bool enabled = 1; } +message AutoTunnelResponse {} diff --git a/components/supervisor-api/status.proto b/components/supervisor-api/status.proto index 8d06d04e48839a..9f9924b2921fcd 100644 --- a/components/supervisor-api/status.proto +++ b/components/supervisor-api/status.proto @@ -7,6 +7,7 @@ syntax = "proto3"; package supervisor; import "google/api/annotations.proto"; +import "port.proto"; option go_package = "github.com/gitpod-io/gitpod/supervisor/api"; @@ -139,6 +140,15 @@ message ExposedPortInfo { // action hint on expose OnPortExposedAction on_exposed = 3; } +message TunneledPortInfo { + // target port is the desired port on the remote machine + uint32 target_port = 1; + // visibility determines if the listener on remote machine should accept connections from localhost or network + // visibility none means that the port should not be tunneled + TunnelVisiblity visibility = 2; + // map of remote clients indicates on which remote port each client is listening to + map clients = 3; +} message PortsStatus { // local_port is the port a service actually bound to. Some services bind // to localhost:, in which case they cannot be made accessible from @@ -155,6 +165,10 @@ message PortsStatus { // Exposed provides information when a port is exposed. If this field isn't set, // the port is not available from outside the workspace (i.e. the internet). ExposedPortInfo exposed = 5; + + // Tunneled provides information when a port is tunneled. If not present then + // the port is not tunneled. + TunneledPortInfo tunneled = 6; } message TasksStatusRequest { diff --git a/components/supervisor/cmd/tunnel.go b/components/supervisor/cmd/tunnel.go new file mode 100644 index 00000000000000..e429f27c131d14 --- /dev/null +++ b/components/supervisor/cmd/tunnel.go @@ -0,0 +1,106 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package cmd + +import ( + "context" + "strconv" + "time" + + "github.com/gitpod-io/gitpod/common-go/log" + "github.com/gitpod-io/gitpod/supervisor/api" + "github.com/spf13/cobra" +) + +var tunnelCmd = &cobra.Command{ + Use: "tunnel [targetPort] [visibility]", + Short: "opens a new tunnel", + Args: cobra.RangeArgs(1, 3), + Run: func(cmd *cobra.Command, args []string) { + localPort, err := strconv.Atoi(args[0]) + if err != nil { + log.WithError(err).Fatal("invalid local port") + return + } + targetPort := localPort + if len(args) > 1 { + targetPort, err = strconv.Atoi(args[1]) + if err != nil { + log.WithError(err).Fatal("invalid target port") + } + } + visiblity := api.TunnelVisiblity_host + if len(args) > 2 { + visiblity = api.TunnelVisiblity(api.TunnelVisiblity_value[args[2]]) + } + + client := api.NewPortServiceClient(dialSupervisor()) + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + _, err = client.Tunnel(ctx, &api.TunnelPortRequest{ + Port: uint32(localPort), + TargetPort: uint32(targetPort), + Visibility: visiblity, + }) + if err != nil { + log.WithError(err).Fatal("cannot tunnel") + } + }, +} + +var closeTunnelCmd = &cobra.Command{ + Use: "close ", + Short: "close the tunnel", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + localPort, err := strconv.Atoi(args[0]) + if err != nil { + log.WithError(err).Fatal("invalid local port") + return + } + + client := api.NewPortServiceClient(dialSupervisor()) + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + _, err = client.CloseTunnel(ctx, &api.CloseTunnelRequest{ + Port: uint32(localPort), + }) + if err != nil { + log.WithError(err).Fatal("cannot close the tunnel") + } + }, +} + +var autoTunnelCmd = &cobra.Command{ + Use: "auto ", + Short: "controls auto tunneling", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + enablement, err := strconv.ParseBool(args[0]) + if err != nil { + log.WithError(err).Fatal("invalid enablement") + return + } + + client := api.NewPortServiceClient(dialSupervisor()) + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + _, err = client.AutoTunnel(ctx, &api.AutoTunnelRequest{ + Enabled: enablement, + }) + if err != nil { + log.WithError(err).Fatal("cannot to update auto tunnelling enablement") + } + }, +} + +func init() { + rootCmd.AddCommand(tunnelCmd) + tunnelCmd.AddCommand(closeTunnelCmd) + tunnelCmd.AddCommand(autoTunnelCmd) +} diff --git a/components/supervisor/go.mod b/components/supervisor/go.mod index 738590e8cdc8e3..c16ab8a3bca742 100644 --- a/components/supervisor/go.mod +++ b/components/supervisor/go.mod @@ -7,8 +7,6 @@ replace github.com/docker/distribution v2.7.1+incompatible => github.com/docker/ replace github.com/docker/docker v1.13.1 => github.com/docker/engine v0.0.0-20190822205725-ed20165a37b4 // leeway ignore -replace github.com/jpillora/chisel => github.com/csweichel/chisel v1.7.6-0.20210413131541-40f425137158 // leeway ignore - require ( github.com/Netflix/go-env v0.0.0-20200908232752-3e802f601e28 github.com/creack/pty v1.1.11 @@ -18,17 +16,19 @@ require ( github.com/gitpod-io/gitpod/gitpod-protocol v0.0.0-00010101000000-000000000000 github.com/gitpod-io/gitpod/supervisor/api v0.0.0-00010101000000-000000000000 github.com/golang/mock v1.5.0 + github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.5 github.com/google/uuid v1.1.4 + github.com/gorilla/websocket v1.4.2 github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 github.com/grpc-ecosystem/grpc-gateway/v2 v2.2.0 - github.com/jpillora/chisel v1.7.6 github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f github.com/prometheus/procfs v0.6.0 github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 // indirect github.com/sirupsen/logrus v1.7.0 github.com/soheilhy/cmux v0.1.4 github.com/spf13/cobra v1.1.1 + golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d diff --git a/components/supervisor/go.sum b/components/supervisor/go.sum index c0ab782ba563bd..f15c19b57c1576 100644 --- a/components/supervisor/go.sum +++ b/components/supervisor/go.sum @@ -216,8 +216,6 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/csweichel/chisel v1.7.6-0.20210413131541-40f425137158 h1:TO0PBAq9dFt1/uyRCD1ZyuGMUokeAkRPvLLCj3zj24g= -github.com/csweichel/chisel v1.7.6-0.20210413131541-40f425137158/go.mod h1:BC2zg11mTIoyGPUjc2EkTgfz3uUUV93+K9tNYCCU/fw= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= @@ -456,6 +454,7 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/ansi v1.0.2 h1:+Ei5HCAH0xsrQRCT2PDr4mq9r4Gm4tg+arNdXRkB22s= github.com/jpillora/ansi v1.0.2/go.mod h1:D2tT+6uzJvN1nBVQILYWkIdq7zG+b5gcFN5WI/VyjMY= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+PpqnyA= github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8= @@ -823,6 +822,8 @@ golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o= +golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/components/supervisor/pkg/ports/ports.go b/components/supervisor/pkg/ports/ports.go index c03f766342e31a..3ea89e1b0f4263 100644 --- a/components/supervisor/pkg/ports/ports.go +++ b/components/supervisor/pkg/ports/ports.go @@ -32,7 +32,7 @@ const ( ) // NewManager creates a new port manager -func NewManager(exposed ExposedPortsInterface, served ServedPortsObserver, config ConfigInterace, internalPorts ...uint32) *Manager { +func NewManager(exposed ExposedPortsInterface, served ServedPortsObserver, config ConfigInterace, tunneled TunneledPortsInterface, internalPorts ...uint32) *Manager { state := make(map[uint32]*managedPort) internal := make(map[uint32]struct{}) for _, p := range internalPorts { @@ -43,14 +43,18 @@ func NewManager(exposed ExposedPortsInterface, served ServedPortsObserver, confi E: exposed, S: served, C: config, + T: tunneled, - internal: internal, - proxies: make(map[uint32]*localhostProxy), - autoExposed: make(map[uint32]uint32), + internal: internal, + proxies: make(map[uint32]*localhostProxy), + autoExposed: make(map[uint32]uint32), + autoTunneled: make(map[uint32]struct{}), state: state, subscriptions: make(map[*Subscription]struct{}), proxyStarter: startLocalhostProxy, + + autoTunnelEnabled: true, } } @@ -65,15 +69,20 @@ type Manager struct { E ExposedPortsInterface S ServedPortsObserver C ConfigInterace + T TunneledPortsInterface internal map[uint32]struct{} proxies map[uint32]*localhostProxy proxyStarter func(LocalhostPort uint32, GlobalPort uint32) (proxy io.Closer, err error) autoExposed map[uint32]uint32 - configs *Configs - exposed []ExposedPort - served []ServedPort + autoTunneled map[uint32]struct{} + autoTunnelEnabled bool + + configs *Configs + exposed []ExposedPort + served []ServedPort + tunneled []PortTunnelState state map[uint32]*managedPort mu sync.RWMutex @@ -91,6 +100,11 @@ type managedPort struct { LocalhostPort uint32 GlobalPort uint32 + + Tunneled bool + TunneledTargetPort uint32 + TunneledVisibility api.TunnelVisiblity + TunneledClients map[string]uint32 } // Subscription is a Subscription to status updates @@ -131,11 +145,13 @@ func (pm *Manager) Run(ctx context.Context, wg *sync.WaitGroup) { exposedUpdates, exposedErrors := pm.E.Observe(ctx) servedUpdates, servedErrors := pm.S.Observe(ctx) configUpdates, configErrors := pm.C.Observe(ctx) + tunneledUpdates, tunneledErrors := pm.T.Observe(ctx) for { var ( exposed []ExposedPort served []ServedPort configured *Configs + tunneled []PortTunnelState ) select { case exposed = <-exposedUpdates: @@ -153,6 +169,11 @@ func (pm *Manager) Run(ctx context.Context, wg *sync.WaitGroup) { log.Error("configured ports observer stopped") return } + case tunneled = <-tunneledUpdates: + if tunneled == nil { + log.Error("tunneled ports observer stopped") + return + } case err := <-exposedErrors: if err == nil { @@ -172,13 +193,19 @@ func (pm *Manager) Run(ctx context.Context, wg *sync.WaitGroup) { return } log.WithError(err).Warn("error while observing served port configs") + case err := <-tunneledErrors: + if err == nil { + log.Error("tunneled ports observer stopped") + return + } + log.WithError(err).Warn("error while observing tunneled ports") } - if exposed == nil && served == nil && configured == nil { + if exposed == nil && served == nil && configured == nil && tunneled == nil { // we received just an error, but no update continue } - pm.updateState(ctx, exposed, served, configured) + pm.updateState(ctx, exposed, served, configured, tunneled) } } @@ -190,7 +217,7 @@ func (pm *Manager) Status() []*api.PortsStatus { return pm.getStatus() } -func (pm *Manager) updateState(ctx context.Context, exposed []ExposedPort, served []ServedPort, configured *Configs) { +func (pm *Manager) updateState(ctx context.Context, exposed []ExposedPort, served []ServedPort, configured *Configs, tunneled []PortTunnelState) { pm.mu.Lock() defer pm.mu.Unlock() @@ -198,6 +225,10 @@ func (pm *Manager) updateState(ctx context.Context, exposed []ExposedPort, serve pm.exposed = exposed } + if tunneled != nil && !reflect.DeepEqual(pm.tunneled, tunneled) { + pm.tunneled = tunneled + } + if served != nil { var servedKeys []uint32 // to preserve insertion order servedMap := make(map[uint32]ServedPort) @@ -217,6 +248,7 @@ func (pm *Manager) updateState(ctx context.Context, exposed []ExposedPort, serve if !reflect.DeepEqual(pm.served, newServed) { pm.served = newServed pm.updateProxies() + pm.autoTunnel(ctx) } } @@ -247,7 +279,7 @@ func (pm *Manager) updateState(ctx context.Context, exposed []ExposedPort, serve func (pm *Manager) nextState(ctx context.Context) map[uint32]*managedPort { state := make(map[uint32]*managedPort) - // 1. first capture exposed since they don't depend on configured or served ports + // 1. first capture exposed and tunneled since they don't depend on configured or served ports for _, exposed := range pm.exposed { port := exposed.LocalPort if pm.boundInternally(port) { @@ -269,6 +301,23 @@ func (pm *Manager) nextState(ctx context.Context) map[uint32]*managedPort { } } + for _, tunneled := range pm.tunneled { + port := tunneled.Desc.LocalPort + if pm.boundInternally(port) { + continue + } + mp, exists := state[port] + if !exists { + mp = &managedPort{} + state[port] = mp + } + mp.LocalhostPort = port + mp.Tunneled = true + mp.TunneledTargetPort = tunneled.Desc.TargetPort + mp.TunneledVisibility = tunneled.Desc.Visibility + mp.TunneledClients = tunneled.Clients + } + // 2. second capture configured since we don't want to auto expose already exposed ports if pm.configs != nil { pm.configs.ForEach(func(port uint32, config *gitpod.PortConfig) { @@ -372,6 +421,45 @@ func (pm *Manager) autoExpose(ctx context.Context, mp *managedPort, public bool) log.WithField("port", *mp).Info("auto-exposing port") } +func (pm *Manager) autoTunnel(ctx context.Context) { + if !pm.autoTunnelEnabled { + var localPorts []uint32 + for localPort := range pm.autoTunneled { + localPorts = append(localPorts, localPort) + } + // CloseTunnel ensures that everything is closed + pm.autoTunneled = make(map[uint32]struct{}) + _, err := pm.T.CloseTunnel(ctx, localPorts...) + if err != nil { + log.WithError(err).Error("cannot close auto tunneled ports") + } + return + } + var descs []*PortTunnelDescription + for _, served := range pm.served { + if pm.boundInternally(served.Port) { + continue + } + _, autoTunneled := pm.autoTunneled[served.Port] + if !autoTunneled { + descs = append(descs, &PortTunnelDescription{ + LocalPort: served.Port, + TargetPort: served.Port, + Visibility: api.TunnelVisiblity_host, + }) + } + } + autoTunneled, err := pm.T.Tunnel(ctx, &TunnelOptions{ + SkipIfExists: true, + }, descs...) + if err != nil { + log.WithError(err).Error("cannot auto tunnel ports") + } + for _, localPort := range autoTunneled { + pm.autoTunneled[localPort] = struct{}{} + } +} + func (pm *Manager) updateProxies() { opened := make(map[uint32]struct{}, len(pm.served)) for _, p := range pm.served { @@ -508,6 +596,56 @@ func (pm *Manager) Expose(ctx context.Context, port uint32, targetPort uint32) e return err } +// Tunnel opens a new tunnel. +func (pm *Manager) Tunnel(ctx context.Context, desc *PortTunnelDescription) error { + pm.mu.Lock() + defer pm.mu.Unlock() + if pm.boundInternally(desc.LocalPort) { + return xerrors.New("cannot tunnel internal port") + } + tunneled, err := pm.T.Tunnel(ctx, &TunnelOptions{ + SkipIfExists: false, + }, desc) + for _, localPort := range tunneled { + delete(pm.autoTunneled, localPort) + } + return err +} + +// CloseTunnel closes the tunnel. +func (pm *Manager) CloseTunnel(ctx context.Context, port uint32) error { + unlock := true + pm.mu.RLock() + defer func() { + if unlock { + pm.mu.RUnlock() + } + }() + if pm.boundInternally(port) { + return xerrors.New("cannot close internal port tunnel") + } + // we don't need the lock anymore. Let's unlock and make sure the defer doesn't try + // the same thing again. + pm.mu.RUnlock() + unlock = false + + _, err := pm.T.CloseTunnel(ctx, port) + return err +} + +// EstablishTunnel actually establishes the tunnel +func (pm *Manager) EstablishTunnel(ctx context.Context, clientID string, localPort uint32, targetPort uint32) (net.Conn, error) { + return pm.T.EstablishTunnel(ctx, clientID, localPort, targetPort) +} + +// AutoTunnel controls enablement of auto tunneling +func (pm *Manager) AutoTunnel(ctx context.Context, enabled bool) { + pm.mu.Lock() + defer pm.mu.Unlock() + pm.autoTunnelEnabled = enabled + pm.autoTunnel(ctx) +} + var ( // ErrClosed when the port management is stopped ErrClosed = errors.New("closed") @@ -572,6 +710,13 @@ func (pm *Manager) getPortStatus(port uint32) *api.PortsStatus { OnExposed: mp.OnExposed, } } + if mp.Tunneled { + ps.Tunneled = &api.TunneledPortInfo{ + TargetPort: mp.TunneledTargetPort, + Visibility: mp.TunneledVisibility, + Clients: mp.TunneledClients, + } + } return ps } diff --git a/components/supervisor/pkg/ports/ports_test.go b/components/supervisor/pkg/ports/ports_test.go index e7395160f020b3..5943758823e93a 100644 --- a/components/supervisor/pkg/ports/ports_test.go +++ b/components/supervisor/pkg/ports/ports_test.go @@ -7,6 +7,7 @@ package ports import ( "context" "io" + "net" "sync" "testing" "time" @@ -29,12 +30,14 @@ func TestPortsUpdateState(t *testing.T) { instance []*gitpod.PortsItems } type Change struct { - Config *ConfigChange - Served []ServedPort - Exposed []ExposedPort - ConfigErr error - ServedErr error - ExposedErr error + Config *ConfigChange + Served []ServedPort + Exposed []ExposedPort + Tunneled []PortTunnelState + ConfigErr error + ServedErr error + ExposedErr error + TunneledErr error } tests := []struct { Desc string @@ -463,8 +466,12 @@ func TestPortsUpdateState(t *testing.T) { Changes: make(chan *Configs), Error: make(chan error, 1), } + tunneled = &testTunneledPorts{ + Changes: make(chan []PortTunnelState), + Error: make(chan error, 1), + } - pm = NewManager(exposed, served, config, test.InternalPorts...) + pm = NewManager(exposed, served, config, tunneled, test.InternalPorts...) updts [][]*api.PortsStatus ) pm.proxyStarter = func(localPort uint32, globalPort uint32) (io.Closer, error) { @@ -496,6 +503,8 @@ func TestPortsUpdateState(t *testing.T) { defer close(served.Changes) defer close(exposed.Error) defer close(exposed.Changes) + defer close(tunneled.Error) + defer close(tunneled.Changes) for _, c := range test.Changes { if c.Config != nil { @@ -515,6 +524,10 @@ func TestPortsUpdateState(t *testing.T) { exposed.Changes <- c.Exposed } else if c.ExposedErr != nil { exposed.Error <- c.ExposedErr + } else if c.Tunneled != nil { + tunneled.Changes <- c.Tunneled + } else if c.TunneledErr != nil { + tunneled.Error <- c.TunneledErr } } }() @@ -541,6 +554,24 @@ func TestPortsUpdateState(t *testing.T) { } } +type testTunneledPorts struct { + Changes chan []PortTunnelState + Error chan error +} + +func (tep *testTunneledPorts) Observe(ctx context.Context) (<-chan []PortTunnelState, <-chan error) { + return tep.Changes, tep.Error +} +func (tep *testTunneledPorts) Tunnel(ctx context.Context, options *TunnelOptions, descs ...*PortTunnelDescription) ([]uint32, error) { + return nil, nil +} +func (tep *testTunneledPorts) CloseTunnel(ctx context.Context, localPorts ...uint32) ([]uint32, error) { + return nil, nil +} +func (tep *testTunneledPorts) EstablishTunnel(ctx context.Context, clientID string, localPort uint32, targetPort uint32) (net.Conn, error) { + return nil, nil +} + type testConfigService struct { Changes chan *Configs Error chan error @@ -603,7 +634,11 @@ func TestPortsConcurrentSubscribe(t *testing.T) { Changes: make(chan *Configs), Error: make(chan error, 1), } - pm = NewManager(exposed, served, config) + tunneled = &testTunneledPorts{ + Changes: make(chan []PortTunnelState), + Error: make(chan error, 1), + } + pm = NewManager(exposed, served, config, tunneled) ) pm.proxyStarter = func(localPort uint32, globalPort uint32) (io.Closer, error) { return io.NopCloser(nil), nil @@ -622,6 +657,8 @@ func TestPortsConcurrentSubscribe(t *testing.T) { defer close(served.Changes) defer close(exposed.Error) defer close(exposed.Changes) + defer close(tunneled.Error) + defer close(tunneled.Changes) var j uint32 for { diff --git a/components/supervisor/pkg/ports/tunnel.go b/components/supervisor/pkg/ports/tunnel.go new file mode 100644 index 00000000000000..2d83886f99f474 --- /dev/null +++ b/components/supervisor/pkg/ports/tunnel.go @@ -0,0 +1,276 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package ports + +import ( + "context" + "fmt" + "io" + "net" + "sort" + "strconv" + "sync" + + "github.com/gitpod-io/gitpod/supervisor/api" + "golang.org/x/xerrors" +) + +type PortTunnelDescription struct { + LocalPort uint32 + TargetPort uint32 + Visibility api.TunnelVisiblity +} + +type PortTunnelState struct { + Desc PortTunnelDescription + Clients map[string]uint32 +} + +type PortTunnel struct { + State PortTunnelState + Conns map[string]map[net.Conn]struct{} +} + +type TunnelOptions struct { + SkipIfExists bool +} + +// TunneledPortsInterface observes the tunneled ports. +type TunneledPortsInterface interface { + // Observe starts observing the tunneled ports until the context is canceled. + // The list of tunneled ports is always the complete picture, i.e. if a single port changes, + // the whole list is returned. + // When the observer stops operating (because the context as canceled or an irrecoverable + // error occured), the observer will close both channels. + Observe(ctx context.Context) (<-chan []PortTunnelState, <-chan error) + + // Tunnel notifies clients to install listeners on remote machines. + // After that such clients should call EstablishTunnel to forward incoming connections. + Tunnel(ctx context.Context, options *TunnelOptions, descs ...*PortTunnelDescription) ([]uint32, error) + + // CloseTunnel closes tunnels. + CloseTunnel(ctx context.Context, localPorts ...uint32) ([]uint32, error) + + // EstablishTunnel actually establishes the tunnel for an incoming connection on a remote machine. + EstablishTunnel(ctx context.Context, clientID string, localPort uint32, targetPort uint32) (net.Conn, error) +} + +// TunneledPortsService observes the tunneled ports. +type TunneledPortsService struct { + mu *sync.RWMutex + cond *sync.Cond + tunnels map[uint32]*PortTunnel +} + +// NewTunneledPortsService creates a new instance +func NewTunneledPortsService(debugEnable bool) *TunneledPortsService { + var mu sync.RWMutex + return &TunneledPortsService{ + mu: &mu, + cond: sync.NewCond(&mu), + tunnels: make(map[uint32]*PortTunnel), + } +} + +type tunnelConn struct { + net.Conn + once sync.Once + closeErr error + onDidClose func() +} + +func (c *tunnelConn) Close() error { + c.once.Do(func() { + c.closeErr = c.Conn.Close() + c.onDidClose() + }) + return c.closeErr +} + +// Observe starts observing the tunneled ports until the context is canceled. +func (p *TunneledPortsService) Observe(ctx context.Context) (<-chan []PortTunnelState, <-chan error) { + var ( + errchan = make(chan error, 1) + reschan = make(chan []PortTunnelState) + ) + + go func() { + defer close(errchan) + defer close(reschan) + + p.cond.L.Lock() + defer p.cond.L.Unlock() + for { + var i int + res := make([]PortTunnelState, len(p.tunnels)) + for _, port := range p.tunnels { + res[i] = port.State + i++ + } + reschan <- res + + p.cond.Wait() + if ctx.Err() != nil { + return + } + } + }() + + return reschan, errchan +} + +func (desc *PortTunnelDescription) validate() (err error) { + if desc.LocalPort <= 0 || desc.LocalPort > 0xFFFF { + return fmt.Errorf("bad local port: %d", desc.LocalPort) + } + if desc.TargetPort < 0 || desc.TargetPort > 0xFFFF { + return fmt.Errorf("bad target port: %d", desc.TargetPort) + } + return nil +} + +// Tunnel opens new tunnels. +func (p *TunneledPortsService) Tunnel(ctx context.Context, options *TunnelOptions, descs ...*PortTunnelDescription) (tunneled []uint32, err error) { + var shouldNotify bool + p.cond.L.Lock() + defer p.cond.L.Unlock() + for _, desc := range descs { + descErr := desc.validate() + if descErr != nil { + if err == nil { + err = descErr + } else { + err = fmt.Errorf("%s\n%s", err, descErr) + } + continue + } + tunnel, tunnelExists := p.tunnels[desc.LocalPort] + if !tunnelExists { + tunnel = &PortTunnel{ + State: PortTunnelState{ + Clients: make(map[string]uint32), + }, + Conns: make(map[string]map[net.Conn]struct{}), + } + p.tunnels[desc.LocalPort] = tunnel + } else if options.SkipIfExists { + continue + } + tunnel.State.Desc = *desc + shouldNotify = true + tunneled = append(tunneled, desc.LocalPort) + } + if shouldNotify { + p.cond.Broadcast() + } + return tunneled, err +} + +// CloseTunnel closes tunnels. +func (p *TunneledPortsService) CloseTunnel(ctx context.Context, localPorts ...uint32) (closedPorts []uint32, err error) { + var closed []*PortTunnel + p.cond.L.Lock() + for _, localPort := range localPorts { + tunnel, existsTunnel := p.tunnels[localPort] + if !existsTunnel { + continue + } + delete(p.tunnels, localPort) + closed = append(closed, tunnel) + closedPorts = append(closedPorts, localPort) + } + if len(closed) > 0 { + p.cond.Broadcast() + } + p.cond.L.Unlock() + for _, tunnel := range closed { + for _, conns := range tunnel.Conns { + for conn := range conns { + closeErr := conn.Close() + if closeErr == nil { + continue + } + if err == nil { + err = closeErr + } else { + err = fmt.Errorf("%s\n%s", err, closeErr) + } + } + } + } + return closedPorts, err +} + +// EstablishTunnel actually establishes the tunnel +func (p *TunneledPortsService) EstablishTunnel(ctx context.Context, clientID string, localPort uint32, targetPort uint32) (net.Conn, error) { + p.cond.L.Lock() + defer p.cond.L.Unlock() + + tunnel, tunnelExists := p.tunnels[localPort] + if tunnelExists { + expectedTargetPort, clientExists := tunnel.State.Clients[clientID] + if clientExists && expectedTargetPort != targetPort { + return nil, xerrors.Errorf("client '%s': %d:%d is already tunneling", clientID, localPort, targetPort) + } + } else { + return nil, xerrors.Errorf("client '%s': '%d' tunnel does not exist", clientID, localPort) + } + + addr := net.JoinHostPort("localhost", strconv.FormatInt(int64(localPort), 10)) + conn, err := net.Dial("tcp", addr) + if err != nil { + return nil, err + } + var result net.Conn + result = &tunnelConn{ + Conn: conn, + onDidClose: func() { + p.cond.L.Lock() + defer p.cond.L.Unlock() + _, existsTunnel := p.tunnels[localPort] + if !existsTunnel { + return + } + delete(tunnel.Conns[clientID], result) + if len(tunnel.Conns[clientID]) == 0 { + delete(tunnel.State.Clients, clientID) + } + p.cond.Broadcast() + }, + } + if tunnel.Conns[clientID] == nil { + tunnel.Conns[clientID] = make(map[net.Conn]struct{}) + } + tunnel.Conns[clientID][result] = struct{}{} + tunnel.State.Clients[clientID] = targetPort + p.cond.Broadcast() + return result, nil +} + +// Snapshot writes a snapshot to w. +func (p *TunneledPortsService) Snapshot(w io.Writer) { + p.mu.RLock() + defer p.mu.RUnlock() + + localPorts := make([]uint32, 0, len(p.tunnels)) + for k := range p.tunnels { + localPorts = append(localPorts, k) + } + sort.Slice(localPorts, func(i, j int) bool { return localPorts[i] < localPorts[j] }) + + for _, localPort := range localPorts { + tunnel := p.tunnels[localPort] + fmt.Fprintf(w, "Local Port: %d\n", tunnel.State.Desc.LocalPort) + fmt.Fprintf(w, "Target Port: %d\n", tunnel.State.Desc.TargetPort) + visibilty := api.TunnelVisiblity_name[int32(tunnel.State.Desc.Visibility)] + fmt.Fprintf(w, "Visibility: %s\n", visibilty) + for clientID, remotePort := range tunnel.State.Clients { + fmt.Fprintf(w, "Client: %s\n", clientID) + fmt.Fprintf(w, " Remote Port: %d\n", remotePort) + fmt.Fprintf(w, " Tunnel Count: %d\n", len(tunnel.Conns[clientID])) + } + fmt.Fprintf(w, "\n") + } +} diff --git a/components/supervisor/pkg/ports/tunnel_test.go b/components/supervisor/pkg/ports/tunnel_test.go new file mode 100644 index 00000000000000..1efec7def7f8eb --- /dev/null +++ b/components/supervisor/pkg/ports/tunnel_test.go @@ -0,0 +1,184 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package ports + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "strconv" + "strings" + "sync" + "testing" + + "github.com/gitpod-io/gitpod/supervisor/api" + "github.com/google/go-cmp/cmp" + "golang.org/x/sync/errgroup" +) + +// TODO(ak) add reverse test +func TestLocalPortTunneling(t *testing.T) { + updates := make(chan []PortTunnelState, 4) + assertUpdate := func(expectation []PortTunnelState) { + update := <-updates + if diff := cmp.Diff(expectation, update); diff != "" { + t.Errorf("unexpected exposures (-want +got):\n%s", diff) + } + } + + doneCtx, done := context.WithCancel(context.Background()) + eg, ctx := errgroup.WithContext(context.Background()) + service := NewTunneledPortsService(false) + tunneled, errors := service.Observe(ctx) + eg.Go(func() error { + for { + select { + case <-doneCtx.Done(): + return nil + case ports := <-tunneled: + if ports == nil { + close(updates) + return nil + } + updates <- ports + case err := <-errors: + return err + } + } + }) + assertUpdate([]PortTunnelState{}) + + localPort, err := availablePort() + if err != nil { + t.Fatal(err) + } + localListener, err := net.Listen("tcp", "127.0.0.1:"+strconv.FormatInt(int64(localPort), 10)) + if err != nil { + t.Fatal(err) + } + fmt.Printf("local service is listening on %d\n", localPort) + eg.Go(func() error { + go func() { + <-doneCtx.Done() + localListener.Close() + }() + localServer := http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, _ := ioutil.ReadAll(r.Body) + w.Write(append(b, '!')) + }), + } + localServer.Serve(localListener) + return nil + }) + + targetPort, err := availablePort() + if err != nil { + t.Fatal(err) + } + desc := PortTunnelDescription{ + LocalPort: localPort, + TargetPort: targetPort, + Visibility: api.TunnelVisiblity_host, + } + _, err = service.Tunnel(ctx, &TunnelOptions{ + SkipIfExists: false, + }, &PortTunnelDescription{ + LocalPort: localPort, + TargetPort: targetPort, + Visibility: api.TunnelVisiblity_host, + }) + if err != nil { + t.Fatal(err) + } + fmt.Printf("%d:%d tunnel has been created\n", localPort, targetPort) + assertUpdate([]PortTunnelState{{Desc: desc, Clients: map[string]uint32{}}}) + + targetAddr := "127.0.0.1:" + strconv.FormatInt(int64(targetPort), 10) + proxyAddr, err := net.ResolveTCPAddr("tcp", targetAddr) + if err != nil { + t.Fatal(err) + } + proxyListener, err := net.ListenTCP("tcp", proxyAddr) + if err != nil { + t.Fatal(err) + } + fmt.Printf("target proxy is listening on %d\n", targetPort) + eg.Go(func() error { + defer proxyListener.Close() + + src, err := proxyListener.Accept() + if err != nil { + return err + } + defer src.Close() + + dst, err := service.EstablishTunnel(ctx, "test", localPort, targetPort) + if err != nil { + return err + } + defer dst.Close() + + done := make(chan struct{}) + var once sync.Once + go func() { + io.Copy(src, dst) + once.Do(func() { close(done) }) + }() + go func() { + io.Copy(dst, src) + once.Do(func() { close(done) }) + }() + <-done + return nil + }) + + // actually open ssh channel + resp, err := http.Post("http://"+targetAddr, "text/plain", strings.NewReader("Hello World")) + if err != nil { + t.Fatal(err) + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatal(err) + } + if string(body) != ("Hello World!") { + t.Fatal("wrong resp") + } + assertUpdate([]PortTunnelState{{Desc: desc, Clients: map[string]uint32{"test": targetPort}}}) + + _, err = service.CloseTunnel(ctx, localPort) + if err != nil { + t.Fatal(err) + } + assertUpdate([]PortTunnelState{}) + + done() + + err = eg.Wait() + if err != nil && err != context.Canceled { + t.Error(err) + } +} + +func availablePort() (uint32, error) { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return 0, err + } + l.Close() + _, parsed, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + return 0, err + } + port, err := strconv.Atoi(parsed) + if err != nil { + return 0, err + } + return uint32(port), nil +} diff --git a/components/supervisor/pkg/supervisor/services.go b/components/supervisor/pkg/supervisor/services.go index 6569783181d9f8..1eb34599e4aefc 100644 --- a/components/supervisor/pkg/supervisor/services.go +++ b/components/supervisor/pkg/supervisor/services.go @@ -6,6 +6,7 @@ package supervisor import ( "context" + "io" "os" "strings" "sync" @@ -693,3 +694,109 @@ func (state *InMemoryContentState) ContentSource() (src csapi.WorkspaceInitSourc } return state.contentSource, true } + +type portService struct { + portsManager *ports.Manager + + api.UnimplementedPortServiceServer +} + +func (s *portService) RegisterGRPC(srv *grpc.Server) { + api.RegisterPortServiceServer(srv, s) +} + +func (s *portService) RegisterREST(mux *runtime.ServeMux, grpcEndpoint string) error { + return api.RegisterPortServiceHandlerFromEndpoint(context.Background(), mux, grpcEndpoint, []grpc.DialOption{grpc.WithInsecure()}) +} + +// Tunnel opens a new tunnel. +func (s *portService) Tunnel(ctx context.Context, req *api.TunnelPortRequest) (*api.TunnelPortResponse, error) { + err := s.portsManager.Tunnel(ctx, &ports.PortTunnelDescription{ + LocalPort: req.Port, + TargetPort: req.TargetPort, + Visibility: req.Visibility, + }) + if err != nil { + return nil, err + } + return &api.TunnelPortResponse{}, nil +} + +// CloseTunnel closes the tunnel. +func (s *portService) CloseTunnel(ctx context.Context, req *api.CloseTunnelRequest) (*api.CloseTunnelResponse, error) { + err := s.portsManager.CloseTunnel(ctx, req.Port) + if err != nil { + return nil, err + } + return &api.CloseTunnelResponse{}, nil +} + +// EstablishTunnel actually establishes the tunnel +func (s *portService) EstablishTunnel(stream api.PortService_EstablishTunnelServer) error { + req, err := stream.Recv() + if err != nil { + return status.Error(codes.Internal, err.Error()) + } + desc := req.GetDesc() + if desc != nil { + return status.Error(codes.FailedPrecondition, "first request should be a desc") + } + + tunnel, err := s.portsManager.EstablishTunnel(stream.Context(), desc.ClientId, desc.Port, desc.TargetPort) + if err != nil { + return status.Errorf(codes.Internal, "failed establish the tunnel: %v", err) + } + defer tunnel.Close() + + errChan := make(chan error) + + go func() { + for { + req, err := stream.Recv() + if err != nil { + errChan <- err + return + } + + data := req.GetData() + if data == nil { + errChan <- status.Errorf(codes.InvalidArgument, "unexped req, expected data: %v", req) + return + } + _, err = tunnel.Write(data) + if err != nil { + errChan <- status.Errorf(codes.Internal, "unable to write to connection: %v", err) + return + } + } + }() + + go func() { + for { + buff := make([]byte, 4096) + bytesRead, err := tunnel.Read(buff) + if err != nil { + errChan <- err + return + } + + err = stream.Send(&api.EstablishTunnelResponse{Data: buff[0:bytesRead]}) + if err != nil { + errChan <- err + return + } + } + }() + + returnedError := <-errChan + if returnedError == io.EOF { + return nil + } + return status.Error(codes.Internal, returnedError.Error()) +} + +// AutoTunnel controls enablement of auto tunneling +func (s *portService) AutoTunnel(ctx context.Context, req *api.AutoTunnelRequest) (*api.AutoTunnelResponse, error) { + s.portsManager.AutoTunnel(ctx, req.Enabled) + return &api.AutoTunnelResponse{}, nil +} diff --git a/components/supervisor/pkg/supervisor/supervisor.go b/components/supervisor/pkg/supervisor/supervisor.go index e3e32b34fc3997..a6c18e58dc8ef9 100644 --- a/components/supervisor/pkg/supervisor/supervisor.go +++ b/components/supervisor/pkg/supervisor/supervisor.go @@ -6,9 +6,12 @@ package supervisor import ( "context" + "crypto/rand" + "crypto/rsa" "crypto/sha256" "encoding/json" "fmt" + "io" "io/ioutil" "net" "net/http" @@ -23,9 +26,12 @@ import ( "syscall" "time" + "github.com/golang/protobuf/proto" + "github.com/gorilla/websocket" grpcruntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/prometheus/procfs" "github.com/soheilhy/cmux" + "golang.org/x/crypto/ssh" "golang.org/x/sys/unix" "google.golang.org/grpc" @@ -43,7 +49,6 @@ import ( "github.com/gitpod-io/gitpod/supervisor/pkg/terminal" grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus" - chisel "github.com/jpillora/chisel/server" ) var ( @@ -124,6 +129,22 @@ func Run(options ...RunOption) { } } + tunneledPortsService := ports.NewTunneledPortsService(cfg.DebugEnable) + _, err = tunneledPortsService.Tunnel(context.Background(), &ports.TunnelOptions{ + SkipIfExists: false, + }, &ports.PortTunnelDescription{ + LocalPort: uint32(cfg.APIEndpointPort), + TargetPort: uint32(cfg.APIEndpointPort), + Visibility: api.TunnelVisiblity_host, + }, &ports.PortTunnelDescription{ + LocalPort: uint32(cfg.SSHPort), + TargetPort: uint32(cfg.SSHPort), + Visibility: api.TunnelVisiblity_host, + }) + if err != nil { + log.WithError(err).Warn("cannot tunnel internal ports") + } + ctx, cancel := context.WithCancel(context.Background()) var ( shutdown = make(chan struct{}) @@ -137,6 +158,7 @@ func Run(options ...RunOption) { RefreshInterval: 2 * time.Second, }, ports.NewConfigService(cfg.WorkspaceID, gitpodConfigService, gitpodService), + tunneledPortsService, uint32(cfg.IDEPort), uint32(cfg.APIEndpointPort), uint32(cfg.SSHPort), @@ -171,6 +193,7 @@ func Run(options ...RunOption) { notificationService, &InfoService{cfg: cfg, ContentState: cstate}, &ControlService{portsManager: portMgmt}, + &portService{portsManager: portMgmt}, } apiServices = append(apiServices, additionalServices...) @@ -192,7 +215,7 @@ func Run(options ...RunOption) { wg.Add(1) go startContentInit(ctx, cfg, &wg, cstate) wg.Add(1) - go startAPIEndpoint(ctx, cfg, &wg, apiServices, apiEndpointOpts...) + go startAPIEndpoint(ctx, cfg, &wg, apiServices, tunneledPortsService, apiEndpointOpts...) wg.Add(1) go startSSHServer(ctx, cfg, &wg) wg.Add(1) @@ -582,7 +605,7 @@ func isBlacklistedEnvvar(name string) bool { return false } -func startAPIEndpoint(ctx context.Context, cfg *Config, wg *sync.WaitGroup, services []RegisterableService, opts ...grpc.ServerOption) { +func startAPIEndpoint(ctx context.Context, cfg *Config, wg *sync.WaitGroup, services []RegisterableService, tunneled *ports.TunneledPortsService, opts ...grpc.ServerOption) { defer wg.Done() defer log.Debug("startAPIEndpoint shutdown") @@ -616,26 +639,32 @@ func startAPIEndpoint(ctx context.Context, cfg *Config, wg *sync.WaitGroup, serv } go grpcServer.Serve(grpcMux) - // Register chisel before the generic HTTP1 catch-all which would otherwise catch chisel, too - chiselMux := m.Match(cmux.HTTP1HeaderFieldPrefix("Sec-WebSocket-Protocol", "chisel-")) - chs, err := chisel.NewServer(&chisel.Config{ - Listener: chiselMux, - Reverse: true, - KeepAlive: 10 * time.Second, - }) - if err != nil { - log.WithError(err).Fatal("cannot start chisel server") - } - err = chs.Start("", strconv.Itoa(cfg.APIEndpointPort)) - if err != nil { - log.WithError(err).Fatal("cannot start chisel server") - } - httpMux := m.Match(cmux.HTTP1Fast()) routes := http.NewServeMux() routes.Handle("/_supervisor/v1/", http.StripPrefix("/_supervisor", restMux)) + upgrader := websocket.Upgrader{} + routes.Handle("/_supervisor/tunnel", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + wsConn, err := upgrader.Upgrade(rw, r, nil) + if err != nil { + log.WithError(err).Error("tunnel: upgrade to the WebSocket protocol failed") + return + } + conn, err := gitpod.NewWebsocketConnection(ctx, wsConn, func(staleErr error) { + log.WithError(staleErr).Error("tunnel: closing stale connection") + }) + if err != nil { + log.WithError(err).Error("tunnel: upgrade to the WebSocket protocol failed") + return + } + tunnelOverWebSocket(tunneled, conn) + })) routes.Handle("/_supervisor/frontend", http.FileServer(http.Dir(cfg.FrontendLocation))) if cfg.DebugEnable { + routes.Handle("/_supervisor/debug/tunnels", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + rw.Header().Set("X-Content-Type-Options", "nosniff") + rw.Header().Set("Content-Type", "text/plain; charset=utf-8") + tunneled.Snapshot(rw) + })) routes.Handle("/_supervisor"+pprof.Path, http.StripPrefix("/_supervisor", pprof.Handler())) } go http.Serve(httpMux, routes) @@ -647,6 +676,83 @@ func startAPIEndpoint(ctx context.Context, cfg *Config, wg *sync.WaitGroup, serv l.Close() } +func tunnelOverWebSocket(tunneled *ports.TunneledPortsService, conn *gitpod.WebsocketConnection) { + hostKey, err := generateHostKey() + if err != nil { + log.WithError(err).Error("tunnel: failed to generate host key") + conn.Close() + return + } + config := &ssh.ServerConfig{ + NoClientAuth: true, + } + config.AddHostKey(hostKey) + sshConn, chans, reqs, err := ssh.NewServerConn(conn, config) + if err != nil { + log.WithError(err).Error("tunnel: ssh connection handshake failed") + return + } + go func() { + conn.Wait() + sshConn.Close() + }() + go ssh.DiscardRequests(reqs) + go func() { + for ch := range chans { + go tunnelOverSSH(conn.Ctx, tunneled, ch) + } + }() + err = sshConn.Wait() + if err != nil { + log.WithError(err).Error("tunnel: ssh connection failed") + } +} + +func generateHostKey() (ssh.Signer, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, err + } + return ssh.NewSignerFromKey(key) +} + +func tunnelOverSSH(ctx context.Context, tunneled *ports.TunneledPortsService, newCh ssh.NewChannel) { + tunnelReq := &api.TunnelPortRequest{} + err := proto.Unmarshal(newCh.ExtraData(), tunnelReq) + if err != nil { + log.WithError(err).Error("tunnel: invalid ssh chan request") + newCh.Reject(ssh.Prohibited, err.Error()) + return + } + + tunnel, err := tunneled.EstablishTunnel(ctx, tunnelReq.ClientId, tunnelReq.Port, tunnelReq.TargetPort) + if err != nil { + log.WithError(err).Error("tunnel: failed to establish") + newCh.Reject(ssh.Prohibited, err.Error()) + return + } + defer tunnel.Close() + + sshChan, reqs, err := newCh.Accept() + if err != nil { + log.WithError(err).Error("tunnel: accepting ssh channel failed") + return + } + defer sshChan.Close() + go ssh.DiscardRequests(reqs) + var wg sync.WaitGroup + wg.Add(2) + go func() { + _, _ = io.Copy(sshChan, tunnel) + wg.Done() + }() + go func() { + _, _ = io.Copy(tunnel, sshChan) + wg.Done() + }() + wg.Wait() +} + func startSSHServer(ctx context.Context, cfg *Config, wg *sync.WaitGroup) { defer wg.Done() diff --git a/yarn.lock b/yarn.lock index 520a4f6d4bff0f..949c8542995076 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4660,7 +4660,7 @@ "@types/history" "*" "@types/react" "*" -"@types/react@*", "@types/react@^17.0.0": +"@types/react@*", "@types/react@17.0.0", "@types/react@^17.0.0": version "17.0.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== @@ -10526,7 +10526,7 @@ follow-redirects@^1.10.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267" integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA== -for-in@^1.0.1, for-in@^1.0.2: +for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -14721,7 +14721,7 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.12.1, nan@^2.13.2, nan@^2.9.2: +nan@2.14.1, nan@^2.12.1, nan@^2.13.2, nan@^2.14.0, nan@^2.9.2: version "2.14.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== @@ -15291,6 +15291,13 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" +oniguruma@7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-7.2.1.tgz#51775834f7819b6e31aa878706aa7f65ad16b07f" + integrity sha512-WPS/e1uzhswPtJSe+Zls/kAj27+lEqZjCmRSjnYk/Z4L2Mu+lJC2JWtkZhPJe4kZeTQfz7ClcLyXlI4J68MG2w== + dependencies: + nan "^2.14.0" + open@^7.0.2: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" @@ -17466,7 +17473,7 @@ react-dev-utils@^11.0.3: strip-ansi "6.0.0" text-table "0.2.0" -react-dom@^17.0.1: +react-dom@17.0.1, react-dom@^17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6" integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug== @@ -17595,7 +17602,7 @@ react-scripts@^4.0.3: optionalDependencies: fsevents "^2.1.3" -react@^17.0.1: +react@17.0.1, react@^17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127" integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w== @@ -20803,11 +20810,24 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== -vscode-jsonrpc@^5.0.0: +vscode-jsonrpc@^5.0.0, vscode-jsonrpc@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== +vscode-languageserver-protocol@3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== + dependencies: + vscode-jsonrpc "^5.0.1" + vscode-languageserver-types "3.15.1" + +vscode-languageserver-types@3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== + vscode-uri@^1.0.1: version "1.0.8" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.8.tgz#9769aaececae4026fb6e22359cb38946580ded59"