feat: add s suffix to use HTTPS for ports (#12862)

This commit is contained in:
Garrett Delfosse 2024-04-09 12:06:22 -04:00 committed by GitHub
parent 189b8626d0
commit 1d4bf30c0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 150 additions and 6 deletions

View File

@ -40,6 +40,7 @@ func Test_ResolveRequest(t *testing.T) {
// Users can access unhealthy and initializing apps (as of 2024-02).
appNameUnhealthy = "app-unhealthy"
appNameInitializing = "app-initializing"
appNameEndsInS = "app-ends-in-s"
// This agent will never connect, so it will never become "connected".
// Users cannot access unhealthy agents.
@ -166,6 +167,12 @@ func Test_ResolveRequest(t *testing.T) {
Threshold: 1000,
},
},
{
Slug: appNameEndsInS,
DisplayName: appNameEndsInS,
SharingLevel: proto.AppSharingLevel_OWNER,
Url: appURL,
},
},
},
{
@ -644,6 +651,67 @@ func Test_ResolveRequest(t *testing.T) {
require.Equal(t, "http://127.0.0.1:9090", token.AppURL)
})
t.Run("PortSubdomainHTTPSS", func(t *testing.T) {
t.Parallel()
req := (workspaceapps.Request{
AccessMethod: workspaceapps.AccessMethodSubdomain,
BasePath: "/",
UsernameOrID: me.Username,
WorkspaceNameOrID: workspace.Name,
AgentNameOrID: agentName,
AppSlugOrPort: "9090ss",
}).Normalize()
rw := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r.Header.Set(codersdk.SessionTokenHeader, client.SessionToken())
_, ok := workspaceapps.ResolveRequest(rw, r, workspaceapps.ResolveRequestOptions{
Logger: api.Logger,
SignedTokenProvider: api.WorkspaceAppsProvider,
DashboardURL: api.AccessURL,
PathAppBaseURL: api.AccessURL,
AppHostname: api.AppHostname,
AppRequest: req,
})
// should parse as app and fail to find app "9090ss"
require.False(t, ok)
w := rw.Result()
_ = w.Body.Close()
b, err := io.ReadAll(w.Body)
require.NoError(t, err)
require.Contains(t, string(b), "404 - Application Not Found")
})
t.Run("SubdomainEndsInS", func(t *testing.T) {
t.Parallel()
req := (workspaceapps.Request{
AccessMethod: workspaceapps.AccessMethodSubdomain,
BasePath: "/",
UsernameOrID: me.Username,
WorkspaceNameOrID: workspace.Name,
AgentNameOrID: agentName,
AppSlugOrPort: appNameEndsInS,
}).Normalize()
rw := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
r.Header.Set(codersdk.SessionTokenHeader, client.SessionToken())
token, ok := workspaceapps.ResolveRequest(rw, r, workspaceapps.ResolveRequestOptions{
Logger: api.Logger,
SignedTokenProvider: api.WorkspaceAppsProvider,
DashboardURL: api.AccessURL,
PathAppBaseURL: api.AccessURL,
AppHostname: api.AppHostname,
AppRequest: req,
})
require.True(t, ok)
require.Equal(t, req.AppSlugOrPort, token.AppSlugOrPort)
})
t.Run("Terminal", func(t *testing.T) {
t.Parallel()

View File

@ -287,12 +287,20 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
// whether the app is a slug or a port and whether there are multiple agents
// in the workspace or not.
var (
agentNameOrID = r.AgentNameOrID
appURL string
appSharingLevel database.AppSharingLevel
portUint, portUintErr = strconv.ParseUint(r.AppSlugOrPort, 10, 16)
agentNameOrID = r.AgentNameOrID
appURL string
appSharingLevel database.AppSharingLevel
// First check if it's a port-based URL with an optional "s" suffix for HTTPS.
potentialPortStr = strings.TrimSuffix(r.AppSlugOrPort, "s")
portUint, portUintErr = strconv.ParseUint(potentialPortStr, 10, 16)
)
//nolint:nestif
if portUintErr == nil {
protocol := "http"
if strings.HasSuffix(r.AppSlugOrPort, "s") {
protocol = "https"
}
if r.AccessMethod != AccessMethodSubdomain {
// TODO(@deansheather): this should return a 400 instead of a 500.
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
}
// If the app slug is a port number, then route to the port as an
// "anonymous app". We only support HTTP for port-based URLs.
// "anonymous app".
//
// This is only supported for subdomain-based applications.
appURL = fmt.Sprintf("http://127.0.0.1:%d", portUint)
appURL = fmt.Sprintf("%s://127.0.0.1:%d", protocol, portUint)
appSharingLevel = database.AppSharingLevelOwner
// Port sharing authorization

View File

@ -57,6 +57,26 @@ func Test_RequestValidate(t *testing.T) {
AppSlugOrPort: "baz",
},
},
{
name: "OK5",
req: workspaceapps.Request{
AccessMethod: workspaceapps.AccessMethodSubdomain,
BasePath: "/",
UsernameOrID: "foo",
WorkspaceNameOrID: "bar",
AppSlugOrPort: "8080",
},
},
{
name: "OK6",
req: workspaceapps.Request{
AccessMethod: workspaceapps.AccessMethodSubdomain,
BasePath: "/",
UsernameOrID: "foo",
WorkspaceNameOrID: "bar",
AppSlugOrPort: "8080s",
},
},
{
name: "NoAccessMethod",
req: workspaceapps.Request{

View File

@ -222,6 +222,54 @@ func Test_TokenMatchesRequest(t *testing.T) {
},
want: false,
},
{
name: "PortPortocolHTTP",
req: workspaceapps.Request{
AccessMethod: workspaceapps.AccessMethodSubdomain,
Prefix: "yolo--",
BasePath: "/",
UsernameOrID: "foo",
WorkspaceNameOrID: "bar",
AgentNameOrID: "baz",
AppSlugOrPort: "8080",
},
token: workspaceapps.SignedToken{
Request: workspaceapps.Request{
AccessMethod: workspaceapps.AccessMethodSubdomain,
Prefix: "yolo--",
BasePath: "/",
UsernameOrID: "foo",
WorkspaceNameOrID: "bar",
AgentNameOrID: "baz",
AppSlugOrPort: "8080",
},
},
want: true,
},
{
name: "PortPortocolHTTPS",
req: workspaceapps.Request{
AccessMethod: workspaceapps.AccessMethodSubdomain,
Prefix: "yolo--",
BasePath: "/",
UsernameOrID: "foo",
WorkspaceNameOrID: "bar",
AgentNameOrID: "baz",
AppSlugOrPort: "8080s",
},
token: workspaceapps.SignedToken{
Request: workspaceapps.Request{
AccessMethod: workspaceapps.AccessMethodSubdomain,
Prefix: "yolo--",
BasePath: "/",
UsernameOrID: "foo",
WorkspaceNameOrID: "bar",
AgentNameOrID: "baz",
AppSlugOrPort: "8080s",
},
},
want: true,
},
}
for _, c := range cases {