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)