Skip to content

Commit 1d4bf30

Browse files
authored
feat: add s suffix to use HTTPS for ports (#12862)
1 parent 189b862 commit 1d4bf30

File tree

4 files changed

+150
-6
lines changed

4 files changed

+150
-6
lines changed

coderd/workspaceapps/db_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func Test_ResolveRequest(t *testing.T) {
4040
// Users can access unhealthy and initializing apps (as of 2024-02).
4141
appNameUnhealthy = "app-unhealthy"
4242
appNameInitializing = "app-initializing"
43+
appNameEndsInS = "app-ends-in-s"
4344

4445
// This agent will never connect, so it will never become "connected".
4546
// Users cannot access unhealthy agents.
@@ -166,6 +167,12 @@ func Test_ResolveRequest(t *testing.T) {
166167
Threshold: 1000,
167168
},
168169
},
170+
{
171+
Slug: appNameEndsInS,
172+
DisplayName: appNameEndsInS,
173+
SharingLevel: proto.AppSharingLevel_OWNER,
174+
Url: appURL,
175+
},
169176
},
170177
},
171178
{
@@ -644,6 +651,67 @@ func Test_ResolveRequest(t *testing.T) {
644651
require.Equal(t, "http://127.0.0.1:9090", token.AppURL)
645652
})
646653

654+
t.Run("PortSubdomainHTTPSS", func(t *testing.T) {
655+
t.Parallel()
656+
657+
req := (workspaceapps.Request{
658+
AccessMethod: workspaceapps.AccessMethodSubdomain,
659+
BasePath: "/",
660+
UsernameOrID: me.Username,
661+
WorkspaceNameOrID: workspace.Name,
662+
AgentNameOrID: agentName,
663+
AppSlugOrPort: "9090ss",
664+
}).Normalize()
665+
666+
rw := httptest.NewRecorder()
667+
r := httptest.NewRequest("GET", "/", nil)
668+
r.Header.Set(codersdk.SessionTokenHeader, client.SessionToken())
669+
670+
_, ok := workspaceapps.ResolveRequest(rw, r, workspaceapps.ResolveRequestOptions{
671+
Logger: api.Logger,
672+
SignedTokenProvider: api.WorkspaceAppsProvider,
673+
DashboardURL: api.AccessURL,
674+
PathAppBaseURL: api.AccessURL,
675+
AppHostname: api.AppHostname,
676+
AppRequest: req,
677+
})
678+
// should parse as app and fail to find app "9090ss"
679+
require.False(t, ok)
680+
w := rw.Result()
681+
_ = w.Body.Close()
682+
b, err := io.ReadAll(w.Body)
683+
require.NoError(t, err)
684+
require.Contains(t, string(b), "404 - Application Not Found")
685+
})
686+
687+
t.Run("SubdomainEndsInS", func(t *testing.T) {
688+
t.Parallel()
689+
690+
req := (workspaceapps.Request{
691+
AccessMethod: workspaceapps.AccessMethodSubdomain,
692+
BasePath: "/",
693+
UsernameOrID: me.Username,
694+
WorkspaceNameOrID: workspace.Name,
695+
AgentNameOrID: agentName,
696+
AppSlugOrPort: appNameEndsInS,
697+
}).Normalize()
698+
699+
rw := httptest.NewRecorder()
700+
r := httptest.NewRequest("GET", "/", nil)
701+
r.Header.Set(codersdk.SessionTokenHeader, client.SessionToken())
702+
703+
token, ok := workspaceapps.ResolveRequest(rw, r, workspaceapps.ResolveRequestOptions{
704+
Logger: api.Logger,
705+
SignedTokenProvider: api.WorkspaceAppsProvider,
706+
DashboardURL: api.AccessURL,
707+
PathAppBaseURL: api.AccessURL,
708+
AppHostname: api.AppHostname,
709+
AppRequest: req,
710+
})
711+
require.True(t, ok)
712+
require.Equal(t, req.AppSlugOrPort, token.AppSlugOrPort)
713+
})
714+
647715
t.Run("Terminal", func(t *testing.T) {
648716
t.Parallel()
649717

coderd/workspaceapps/request.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -287,12 +287,20 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
287287
// whether the app is a slug or a port and whether there are multiple agents
288288
// in the workspace or not.
289289
var (
290-
agentNameOrID = r.AgentNameOrID
291-
appURL string
292-
appSharingLevel database.AppSharingLevel
293-
portUint, portUintErr = strconv.ParseUint(r.AppSlugOrPort, 10, 16)
290+
agentNameOrID = r.AgentNameOrID
291+
appURL string
292+
appSharingLevel database.AppSharingLevel
293+
// First check if it's a port-based URL with an optional "s" suffix for HTTPS.
294+
potentialPortStr = strings.TrimSuffix(r.AppSlugOrPort, "s")
295+
portUint, portUintErr = strconv.ParseUint(potentialPortStr, 10, 16)
294296
)
297+
//nolint:nestif
295298
if portUintErr == nil {
299+
protocol := "http"
300+
if strings.HasSuffix(r.AppSlugOrPort, "s") {
301+
protocol = "https"
302+
}
303+
296304
if r.AccessMethod != AccessMethodSubdomain {
297305
// TODO(@deansheather): this should return a 400 instead of a 500.
298306
return nil, xerrors.New("port-based URLs are only supported for subdomain-based applications")
@@ -309,10 +317,10 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
309317
}
310318

311319
// If the app slug is a port number, then route to the port as an
312-
// "anonymous app". We only support HTTP for port-based URLs.
320+
// "anonymous app".
313321
//
314322
// This is only supported for subdomain-based applications.
315-
appURL = fmt.Sprintf("http://127.0.0.1:%d", portUint)
323+
appURL = fmt.Sprintf("%s://127.0.0.1:%d", protocol, portUint)
316324
appSharingLevel = database.AppSharingLevelOwner
317325

318326
// Port sharing authorization

coderd/workspaceapps/request_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@ func Test_RequestValidate(t *testing.T) {
5757
AppSlugOrPort: "baz",
5858
},
5959
},
60+
{
61+
name: "OK5",
62+
req: workspaceapps.Request{
63+
AccessMethod: workspaceapps.AccessMethodSubdomain,
64+
BasePath: "/",
65+
UsernameOrID: "foo",
66+
WorkspaceNameOrID: "bar",
67+
AppSlugOrPort: "8080",
68+
},
69+
},
70+
{
71+
name: "OK6",
72+
req: workspaceapps.Request{
73+
AccessMethod: workspaceapps.AccessMethodSubdomain,
74+
BasePath: "/",
75+
UsernameOrID: "foo",
76+
WorkspaceNameOrID: "bar",
77+
AppSlugOrPort: "8080s",
78+
},
79+
},
6080
{
6181
name: "NoAccessMethod",
6282
req: workspaceapps.Request{

coderd/workspaceapps/token_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,54 @@ func Test_TokenMatchesRequest(t *testing.T) {
222222
},
223223
want: false,
224224
},
225+
{
226+
name: "PortPortocolHTTP",
227+
req: workspaceapps.Request{
228+
AccessMethod: workspaceapps.AccessMethodSubdomain,
229+
Prefix: "yolo--",
230+
BasePath: "/",
231+
UsernameOrID: "foo",
232+
WorkspaceNameOrID: "bar",
233+
AgentNameOrID: "baz",
234+
AppSlugOrPort: "8080",
235+
},
236+
token: workspaceapps.SignedToken{
237+
Request: workspaceapps.Request{
238+
AccessMethod: workspaceapps.AccessMethodSubdomain,
239+
Prefix: "yolo--",
240+
BasePath: "/",
241+
UsernameOrID: "foo",
242+
WorkspaceNameOrID: "bar",
243+
AgentNameOrID: "baz",
244+
AppSlugOrPort: "8080",
245+
},
246+
},
247+
want: true,
248+
},
249+
{
250+
name: "PortPortocolHTTPS",
251+
req: workspaceapps.Request{
252+
AccessMethod: workspaceapps.AccessMethodSubdomain,
253+
Prefix: "yolo--",
254+
BasePath: "/",
255+
UsernameOrID: "foo",
256+
WorkspaceNameOrID: "bar",
257+
AgentNameOrID: "baz",
258+
AppSlugOrPort: "8080s",
259+
},
260+
token: workspaceapps.SignedToken{
261+
Request: workspaceapps.Request{
262+
AccessMethod: workspaceapps.AccessMethodSubdomain,
263+
Prefix: "yolo--",
264+
BasePath: "/",
265+
UsernameOrID: "foo",
266+
WorkspaceNameOrID: "bar",
267+
AgentNameOrID: "baz",
268+
AppSlugOrPort: "8080s",
269+
},
270+
},
271+
want: true,
272+
},
225273
}
226274

227275
for _, c := range cases {

0 commit comments

Comments
 (0)