Skip to content

Commit 6a325da

Browse files
authored
Switch from golang.org/x/net/websocket to nhooyr.io/websocket and handle NotFound errors (#105)
* Switch from golang.org/x/net/websocket to nhooyr.io/websocket * Do not attach errors that we can handle to the Gin's context * Add missing newline to "no credentials specified or found, ..." message * Fix potential NPE in ChooseUsernameAndPassword() * Fix type in PortForward() error message in "orchard ssh vm" * Fix potential NPE in Connections() * Use header.Set() for consistency's sake for Authorization header
1 parent 3c3b8e8 commit 6a325da

File tree

9 files changed

+87
-41
lines changed

9 files changed

+87
-41
lines changed

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ require (
1919
github.com/manifoldco/promptui v0.9.0
2020
github.com/mitchellh/go-grpc-net-conn v0.0.0-20200427190222-eb030e4876f0
2121
github.com/penglongli/gin-metrics v0.1.10
22+
github.com/pkg/errors v0.9.1
2223
github.com/prometheus/client_golang v1.15.0
2324
github.com/samber/lo v1.38.1
2425
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
@@ -34,6 +35,7 @@ require (
3435
gopkg.in/natefinch/lumberjack.v2 v2.2.1
3536
gopkg.in/yaml.v3 v3.0.1
3637
howett.net/plist v1.0.0
38+
nhooyr.io/websocket v1.8.7
3739
)
3840

3941
require (
@@ -74,7 +76,7 @@ require (
7476
github.com/inconshreveable/mousetrap v1.0.1 // indirect
7577
github.com/josharian/intern v1.0.0 // indirect
7678
github.com/json-iterator/go v1.1.12 // indirect
77-
github.com/klauspost/compress v1.13.6 // indirect
79+
github.com/klauspost/compress v1.16.7 // indirect
7880
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
7981
github.com/leodido/go-urn v1.2.1 // indirect
8082
github.com/mailru/easyjson v0.7.7 // indirect
@@ -87,7 +89,6 @@ require (
8789
github.com/modern-go/reflect2 v1.0.2 // indirect
8890
github.com/oklog/ulid v1.3.1 // indirect
8991
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
90-
github.com/pkg/errors v0.9.1 // indirect
9192
github.com/pmezard/go-difflib v1.0.0 // indirect
9293
github.com/prometheus/client_model v0.3.0 // indirect
9394
github.com/prometheus/common v0.42.0 // indirect

go.sum

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
108108
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
109109
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
110110
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
111+
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
111112
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
112113
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
113114
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
@@ -155,6 +156,7 @@ github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/Nu
155156
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
156157
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
157158
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
159+
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
158160
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
159161
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
160162
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
@@ -185,6 +187,12 @@ github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWe
185187
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
186188
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
187189
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
190+
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
191+
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
192+
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
193+
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
194+
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
195+
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
188196
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
189197
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
190198
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
@@ -259,6 +267,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
259267
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
260268
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
261269
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
270+
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
271+
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
262272
github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=
263273
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
264274
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
@@ -293,9 +303,11 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR
293303
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
294304
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
295305
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
306+
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
296307
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
297-
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
298308
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
309+
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
310+
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
299311
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
300312
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
301313
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -800,6 +812,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
800812
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
801813
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
802814
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
815+
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
816+
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
803817
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
804818
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
805819
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=

internal/command/ssh/vm.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ func runSSHVM(cmd *cobra.Command, args []string) error {
5656

5757
wsConn, err := client.VMs().PortForward(cmd.Context(), name, 22, wait)
5858
if err != nil {
59-
fmt.Printf("failed to forward an SSH port to VM %s: %v\n", name, err)
60-
61-
return err
59+
return fmt.Errorf("%w: failed to setup port-forwarding to the VM %q: %v", ErrFailed, name, err)
6260
}
6361
defer wsConn.Close()
6462

@@ -78,9 +76,6 @@ func runSSHVM(cmd *cobra.Command, args []string) error {
7876
if err != nil {
7977
return fmt.Errorf("%w: failed to establish an SSH connection: %v", ErrFailed, err)
8078
}
81-
defer func() {
82-
_ = sshConn.Close()
83-
}()
8479

8580
sshClient := ssh.NewClient(sshConn, chans, reqs)
8681

@@ -188,16 +183,16 @@ func ChooseUsernameAndPassword(
188183

189184
// Try to get the credentials from the VM's object stored on controller
190185
vm, err := client.VMs().Get(ctx, vmName)
191-
if err != nil {
192-
fmt.Printf("failed to retrieve VM %s's credentials from the API server: %v\n", vmName, err)
193-
}
194-
195-
if vm.Username != "" && vm.Password != "" {
186+
if err == nil && vm.Username != "" && vm.Password != "" {
196187
return vm.Username, vm.Password
188+
} else if err != nil {
189+
fmt.Fprintf(os.Stderr, "failed to retrieve VM %s's credentials from the API server: %v\n",
190+
vmName, err)
197191
}
198192

199193
// Fall back
200-
_, _ = fmt.Fprintf(os.Stderr, "no credentials specified or found, trying default admin:admin credentials...")
194+
_, _ = fmt.Fprintf(os.Stderr, "no credentials specified or found, "+
195+
"trying default admin:admin credentials...\n")
201196

202197
return "admin", "admin"
203198
}

internal/command/vnc/vm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func runVNCVM(cmd *cobra.Command, args []string) (err error) {
7272

7373
return
7474
}
75+
defer wsConn.Close()
7576

7677
if err := proxy.Connections(wsConn, conn); err != nil {
7778
fmt.Printf("failed to forward port: %v\n", err)

internal/controller/api_vms_portforward.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99
"github.com/cirruslabs/orchard/rpc"
1010
"github.com/gin-gonic/gin"
1111
"github.com/google/uuid"
12-
"golang.org/x/net/websocket"
1312
"net/http"
13+
"nhooyr.io/websocket"
1414
"strconv"
1515
"time"
1616
)
@@ -95,11 +95,26 @@ func (controller *Controller) portForwardVM(ctx *gin.Context) responder.Responde
9595
// worker will asynchronously start port-forwarding so we wait
9696
select {
9797
case fromWorkerConnection := <-boomerangConnCh:
98-
websocket.Handler(func(wsConn *websocket.Conn) {
99-
if err := proxy.Connections(wsConn, fromWorkerConnection); err != nil {
100-
controller.logger.Warnf("failed to port-forward: %v", err)
101-
}
102-
}).ServeHTTP(ctx.Writer, ctx.Request)
98+
wsConn, err := websocket.Accept(ctx.Writer, ctx.Request, &websocket.AcceptOptions{
99+
OriginPatterns: []string{"*"},
100+
})
101+
if err != nil {
102+
return responder.Error(err)
103+
}
104+
105+
expectedMsgType := websocket.MessageBinary
106+
107+
// Backwards compatibility with older Orchard clients
108+
// using "golang.org/x/net/websocket" package
109+
if ctx.Request.Header.Get("User-Agent") == "" {
110+
expectedMsgType = websocket.MessageText
111+
}
112+
113+
wsConnAsNetConn := websocket.NetConn(ctx, wsConn, expectedMsgType)
114+
115+
if err := proxy.Connections(wsConnAsNetConn, fromWorkerConnection); err != nil {
116+
controller.logger.Warnf("failed to port-forward: %v", err)
117+
}
103118

104119
return responder.Empty()
105120
case <-ctx.Done():

internal/proxy/proxy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func Connections(left net.Conn, right net.Conn) (finalErr error) {
3939
recordErr(<-rightErrCh)
4040
}
4141

42-
if strings.Contains(finalErr.Error(), "use of closed network connection") {
42+
if finalErr != nil && strings.Contains(finalErr.Error(), "use of closed network connection") {
4343
finalErr = nil
4444
}
4545

internal/responder/error.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ func Error(err error) Responder {
2020

2121
func (responder *ErrorResponder) Respond(c *gin.Context) {
2222
var code = http.StatusInternalServerError
23+
2324
if errors.Is(responder.err, storepkg.ErrNotFound) {
2425
code = http.StatusNotFound
26+
} else {
27+
_ = c.Error(responder.err)
2528
}
26-
_ = c.Error(responder.err)
29+
2730
c.Status(code)
2831
}

pkg/client/client.go

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ import (
1111
"fmt"
1212
"github.com/cirruslabs/orchard/internal/config"
1313
"github.com/cirruslabs/orchard/internal/netconstants"
14+
"github.com/cirruslabs/orchard/internal/version"
1415
"github.com/cirruslabs/orchard/rpc"
15-
"golang.org/x/net/websocket"
1616
"google.golang.org/grpc/credentials"
1717
"google.golang.org/grpc/credentials/insecure"
1818
"google.golang.org/grpc/metadata"
1919
"io"
20+
"net"
2021
"net/http"
2122
"net/url"
23+
"nhooyr.io/websocket"
2224
)
2325

2426
var (
@@ -184,9 +186,7 @@ func (client *Client) request(
184186
return fmt.Errorf("%w instantiate a request: %v", ErrFailed, err)
185187
}
186188

187-
if client.serviceAccountName != "" && client.serviceAccountToken != "" {
188-
request.SetBasicAuth(client.serviceAccountName, client.serviceAccountToken)
189-
}
189+
client.modifyHeader(request.Header)
190190

191191
response, err := client.httpClient.Do(request)
192192
if err != nil {
@@ -238,10 +238,10 @@ func detailsFromErrorResponseBody(body io.Reader) string {
238238
}
239239

240240
func (client *Client) wsRequest(
241-
_ context.Context,
241+
ctx context.Context,
242242
path string,
243243
params map[string]string,
244-
) (*websocket.Conn, error) {
244+
) (net.Conn, error) {
245245
endpointURL, err := client.parsePath(path)
246246
if err != nil {
247247
return nil, err
@@ -260,20 +260,27 @@ func (client *Client) wsRequest(
260260
}
261261
endpointURL.RawQuery = values.Encode()
262262

263-
config, err := websocket.NewConfig(endpointURL.String(), "http://127.0.0.1/")
264-
if err != nil {
265-
return nil, fmt.Errorf("%w to create WebSocket configuration: %v", ErrFailed, err)
263+
dialOptions := &websocket.DialOptions{
264+
HTTPClient: client.httpClient,
265+
HTTPHeader: make(http.Header),
266266
}
267267

268-
if client.serviceAccountName != "" && client.serviceAccountToken != "" {
269-
authPlain := fmt.Sprintf("%s:%s", client.serviceAccountName, client.serviceAccountToken)
270-
authEncoded := base64.StdEncoding.EncodeToString([]byte(authPlain))
271-
config.Header.Add("Authorization", fmt.Sprintf("Basic %s", authEncoded))
272-
}
268+
client.modifyHeader(dialOptions.HTTPHeader)
269+
270+
conn, resp, err := websocket.Dial(ctx, endpointURL.String(), dialOptions)
271+
if err != nil {
272+
if resp != nil {
273+
_ = resp.Body.Close()
274+
}
273275

274-
config.TlsConfig = client.tlsConfig
276+
if resp.StatusCode == http.StatusNotFound {
277+
err = fmt.Errorf("%w (are you sure this VM exists on the controller?)", err)
278+
}
279+
280+
return nil, err
281+
}
275282

276-
return websocket.DialConfig(config)
283+
return websocket.NetConn(ctx, conn, websocket.MessageBinary), nil
277284
}
278285

279286
func (client *Client) parsePath(path string) (*url.URL, error) {
@@ -291,6 +298,16 @@ func (client *Client) parsePath(path string) (*url.URL, error) {
291298
}, nil
292299
}
293300

301+
func (client *Client) modifyHeader(header http.Header) {
302+
header.Set("User-Agent", fmt.Sprintf("Orchard/%s", version.FullVersion))
303+
304+
if client.serviceAccountName != "" && client.serviceAccountToken != "" {
305+
authPlain := fmt.Sprintf("%s:%s", client.serviceAccountName, client.serviceAccountToken)
306+
authEncoded := base64.StdEncoding.EncodeToString([]byte(authPlain))
307+
header.Set("Authorization", fmt.Sprintf("Basic %s", authEncoded))
308+
}
309+
}
310+
294311
func (client *Client) Check(ctx context.Context) error {
295312
return client.request(ctx, http.MethodGet, "/", nil, nil, nil)
296313
}

pkg/client/vms.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"context"
55
"fmt"
66
"github.com/cirruslabs/orchard/pkg/resource/v1"
7-
"golang.org/x/net/websocket"
7+
"net"
88
"net/http"
99
"strconv"
1010
)
@@ -92,7 +92,7 @@ func (service *VMsService) PortForward(
9292
name string,
9393
port uint16,
9494
waitSeconds uint16,
95-
) (*websocket.Conn, error) {
95+
) (net.Conn, error) {
9696
return service.client.wsRequest(ctx, fmt.Sprintf("vms/%s/port-forward", name),
9797
map[string]string{
9898
"port": strconv.FormatUint(uint64(port), 10),

0 commit comments

Comments
 (0)