Skip to content

Commit de5e515

Browse files
committed
fix: fix workspacesdk to return error on API mismatch
1 parent 30c4b4d commit de5e515

File tree

4 files changed

+903
-3
lines changed

4 files changed

+903
-3
lines changed

codersdk/workspacesdk/connector.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"io"
77
"net/http"
8+
"slices"
89
"sync"
910
"time"
1011

@@ -101,6 +102,9 @@ func (tac *tailnetAPIConnector) run() {
101102
defer close(tac.closed)
102103
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(tac.ctx); {
103104
tailnetClient, err := tac.dial()
105+
if xerrors.Is(err, &codersdk.Error{}) {
106+
return
107+
}
104108
if err != nil {
105109
continue
106110
}
@@ -110,12 +114,17 @@ func (tac *tailnetAPIConnector) run() {
110114
}
111115
}
112116

117+
var permanentErrorStatuses = []int{
118+
http.StatusConflict, // returned if client/agent connections disabled (browser only)
119+
http.StatusBadRequest, // returned if API mismatch
120+
http.StatusNotFound} // returned if user doesn't have permission or agent doesn't exist
121+
113122
func (tac *tailnetAPIConnector) dial() (proto.DRPCTailnetClient, error) {
114123
tac.logger.Debug(tac.ctx, "dialing Coder tailnet v2+ API")
115124
// nolint:bodyclose
116125
ws, res, err := websocket.Dial(tac.ctx, tac.coordinateURL, tac.dialOptions)
117126
if tac.isFirst {
118-
if res != nil && res.StatusCode == http.StatusConflict {
127+
if res != nil && slices.Contains(permanentErrorStatuses, res.StatusCode) {
119128
err = codersdk.ReadBodyAsError(res)
120129
tac.connected <- err
121130
return nil, err

codersdk/workspacesdk/connector_internal_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818

1919
"cdr.dev/slog"
2020
"cdr.dev/slog/sloggers/slogtest"
21+
"github.com/coder/coder/v2/apiversion"
22+
"github.com/coder/coder/v2/coderd/httpapi"
2123
"github.com/coder/coder/v2/codersdk"
2224
"github.com/coder/coder/v2/tailnet"
2325
"github.com/coder/coder/v2/tailnet/proto"
@@ -97,6 +99,45 @@ func TestTailnetAPIConnector_Disconnects(t *testing.T) {
9799
require.NotNil(t, reqDisc.Disconnect)
98100
}
99101

102+
func TestTailnetAPIConnector_UplevelVersion(t *testing.T) {
103+
t.Parallel()
104+
ctx := testutil.Context(t, testutil.WaitShort)
105+
logger := slogtest.Make(t, &slogtest.Options{
106+
//IgnoredErrorIs: append(slogtest.DefaultIgnoredErrorIs,
107+
// io.EOF, // we get EOF when we simulate a DERPMap error
108+
// yamux.ErrSessionShutdown, // coordination can throw these when DERP error tears down session
109+
//),
110+
}).Leveled(slog.LevelDebug)
111+
agentID := uuid.UUID{0x55}
112+
113+
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
114+
sVer := apiversion.New(proto.CurrentMajor, proto.CurrentMinor-1)
115+
116+
// the following matches what Coderd does;
117+
// c.f. coderd/workspaceagents.go: workspaceAgentClientCoordinate
118+
cVer := r.URL.Query().Get("version")
119+
if err := sVer.Validate(cVer); err != nil {
120+
httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{
121+
Message: "Unknown or unsupported API version",
122+
Validations: []codersdk.ValidationError{
123+
{Field: "version", Detail: err.Error()},
124+
},
125+
})
126+
return
127+
}
128+
}))
129+
130+
fConn := newFakeTailnetConn()
131+
132+
uut := runTailnetAPIConnector(ctx, logger, agentID, svr.URL, &websocket.DialOptions{}, fConn)
133+
134+
err := testutil.RequireRecvCtx(ctx, t, uut.connected)
135+
var sdkErr *codersdk.Error
136+
require.ErrorAs(t, err, &sdkErr)
137+
require.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
138+
require.Equal(t, "Unknown or unsupported API version", sdkErr.Message)
139+
}
140+
100141
type fakeTailnetConn struct{}
101142

102143
func (*fakeTailnetConn) UpdatePeers([]*proto.CoordinateResponse_PeerUpdate) error {

enterprise/coderd/workspaceagents_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ func TestBlockNonBrowser(t *testing.T) {
4646
},
4747
})
4848
r := setupWorkspaceAgent(t, client, user, 0)
49+
ctx := testutil.Context(t, testutil.WaitShort)
4950
//nolint:gocritic // Testing that even the owner gets blocked.
50-
_, err := workspacesdk.New(client).DialAgent(context.Background(), r.sdkAgent.ID, nil)
51+
_, err := workspacesdk.New(client).DialAgent(ctx, r.sdkAgent.ID, nil)
5152
var apiErr *codersdk.Error
5253
require.ErrorAs(t, err, &apiErr)
5354
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
@@ -65,8 +66,9 @@ func TestBlockNonBrowser(t *testing.T) {
6566
},
6667
})
6768
r := setupWorkspaceAgent(t, client, user, 0)
69+
ctx := testutil.Context(t, testutil.WaitShort)
6870
//nolint:gocritic // Testing RBAC is not the point of this test.
69-
conn, err := workspacesdk.New(client).DialAgent(context.Background(), r.sdkAgent.ID, nil)
71+
conn, err := workspacesdk.New(client).DialAgent(ctx, r.sdkAgent.ID, nil)
7072
require.NoError(t, err)
7173
_ = conn.Close()
7274
})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy