Skip to content

Commit 866eeed

Browse files
committed
Add proxying based on path
1 parent 934b1ff commit 866eeed

File tree

7 files changed

+158
-92
lines changed

7 files changed

+158
-92
lines changed

coderd/coderd.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package coderd
22

33
import (
44
"context"
5-
"crypto/cipher"
65
"crypto/x509"
76
"fmt"
87
"net/http"
@@ -36,11 +35,10 @@ import (
3635

3736
// Options are requires parameters for Coder to start.
3837
type Options struct {
39-
AccessURL *url.URL
40-
WildcardURL *url.URL
41-
Logger slog.Logger
42-
Database database.Store
43-
Pubsub database.Pubsub
38+
AccessURL *url.URL
39+
Logger slog.Logger
40+
Database database.Store
41+
Pubsub database.Pubsub
4442

4543
AgentConnectionUpdateFrequency time.Duration
4644
// APIRateLimit is the minutely throughput rate limit per user or ip.
@@ -57,9 +55,6 @@ type Options struct {
5755
SSHKeygenAlgorithm gitsshkey.Algorithm
5856
TURNServer *turnconn.Server
5957
TracerProvider *sdktrace.TracerProvider
60-
// WildcardCipher is used to encrypt session tokens so that authentication
61-
// can be securely transferred to the wildcard host.
62-
WildcardCipher cipher.AEAD
6358
}
6459

6560
// New constructs a Coder API handler.
@@ -109,6 +104,7 @@ func New(options *Options) *API {
109104
httpmw.ExtractUserParam(api.Database),
110105
authRolesMiddleware,
111106
)
107+
r.Get("/", api.workspaceAppsProxyPath)
112108
})
113109

114110
r.Route("/api/v2", func(r chi.Router) {

coderd/httpmw/wildcard.go

Lines changed: 0 additions & 24 deletions
This file was deleted.

coderd/httpmw/wildcard_test.go

Lines changed: 0 additions & 38 deletions
This file was deleted.

coderd/workspaceagents.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -462,11 +462,10 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp {
462462
apps := make([]codersdk.WorkspaceApp, 0)
463463
for _, dbApp := range dbApps {
464464
apps = append(apps, codersdk.WorkspaceApp{
465-
ID: dbApp.ID,
466-
Name: dbApp.Name,
467-
Command: dbApp.Command.String,
468-
AccessURL: dbApp.Url.String,
469-
Icon: dbApp.Icon,
465+
ID: dbApp.ID,
466+
Name: dbApp.Name,
467+
Command: dbApp.Command.String,
468+
Icon: dbApp.Icon,
470469
})
471470
}
472471
return apps

coderd/workspaceapps.go

Lines changed: 113 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,133 @@
11
package coderd
22

33
import (
4+
"database/sql"
5+
"errors"
6+
"fmt"
47
"net/http"
8+
"net/http/httputil"
9+
"net/url"
10+
"strings"
511

6-
"github.com/coder/coder/coderd/database"
12+
"github.com/go-chi/chi/v5"
713
"github.com/google/uuid"
8-
)
914

10-
// workspaceAppsAuthWildcard authenticates the wildcard domain.
11-
func (api *API) workspaceAppsAuthWildcard(rw http.ResponseWriter, r *http.Request) {
12-
// r.URL.Query().Get("redirect")
15+
"github.com/coder/coder/coderd/database"
16+
"github.com/coder/coder/coderd/httpapi"
17+
"github.com/coder/coder/coderd/httpmw"
18+
)
1319

14-
}
20+
func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request) {
21+
user := httpmw.UserParam(r)
22+
// This can be in the form of: "<workspace-name>.[workspace-agent]" or "<workspace-name>"
23+
workspaceWithAgent := chi.URLParam(r, "workspaceagent")
24+
workspaceParts := strings.Split(workspaceWithAgent, ".")
1525

16-
func (api *API) workspaceAppsProxyWildcard(rw http.ResponseWriter, r *http.Request) {
26+
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{
27+
OwnerID: user.ID,
28+
Name: workspaceParts[0],
29+
})
30+
if errors.Is(err, sql.ErrNoRows) {
31+
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
32+
Message: "workspace not found",
33+
})
34+
return
35+
}
36+
if err != nil {
37+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
38+
Message: fmt.Sprintf("get workspace: %s", err),
39+
})
40+
return
41+
}
1742

18-
}
43+
build, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)
44+
if err != nil {
45+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
46+
Message: fmt.Sprintf("get workspace build: %s", err),
47+
})
48+
return
49+
}
1950

20-
func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request) {
21-
conn, err := api.dialWorkspaceAgent(r, uuid.Nil)
51+
resources, err := api.Database.GetWorkspaceResourcesByJobID(r.Context(), build.JobID)
2252
if err != nil {
53+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
54+
Message: fmt.Sprintf("get workspace resources: %s", err),
55+
})
2356
return
2457
}
58+
resourceIDs := make([]uuid.UUID, 0)
59+
for _, resource := range resources {
60+
resourceIDs = append(resourceIDs, resource.ID)
61+
}
62+
agents, err := api.Database.GetWorkspaceAgentsByResourceIDs(r.Context(), resourceIDs)
63+
if err != nil {
64+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
65+
Message: fmt.Sprintf("get workspace agents: %s", err),
66+
})
67+
return
68+
}
69+
if len(agents) == 0 {
70+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
71+
Message: "no agents exist",
72+
})
73+
}
74+
75+
agent := agents[0]
76+
if len(workspaceParts) > 1 {
77+
for _, otherAgent := range agents {
78+
if otherAgent.Name == workspaceParts[1] {
79+
agent = otherAgent
80+
break
81+
}
82+
}
83+
}
84+
2585
app, err := api.Database.GetWorkspaceAppByAgentIDAndName(r.Context(), database.GetWorkspaceAppByAgentIDAndNameParams{
26-
AgentID: uuid.Nil,
27-
Name: "something",
86+
AgentID: agent.ID,
87+
Name: chi.URLParam(r, "application"),
2888
})
89+
if errors.Is(err, sql.ErrNoRows) {
90+
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
91+
Message: "application not found",
92+
})
93+
return
94+
}
2995
if err != nil {
96+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
97+
Message: fmt.Sprintf("get workspace app: %s", err),
98+
})
3099
return
31100
}
32-
conn.DialContext(r.Context(), "tcp", "localhost:3000")
101+
if !app.Url.Valid {
102+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
103+
Message: fmt.Sprintf("application does not have a url: %s", err),
104+
})
105+
return
106+
}
107+
108+
appURL, err := url.Parse(app.Url.String)
109+
if err != nil {
110+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
111+
Message: fmt.Sprintf("parse app url: %s", err),
112+
})
113+
return
114+
}
115+
116+
conn, err := api.dialWorkspaceAgent(r, agent.ID)
117+
if err != nil {
118+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
119+
Message: fmt.Sprintf("dial workspace agent: %s", err),
120+
})
121+
return
122+
}
123+
124+
proxy := httputil.NewSingleHostReverseProxy(appURL)
125+
defaultTransport, valid := http.DefaultTransport.(*http.Transport)
126+
if !valid {
127+
panic("dev error: default transport isn't a transport")
128+
}
129+
transport := defaultTransport.Clone()
130+
transport.DialContext = conn.DialContext
131+
proxy.Transport = transport
132+
proxy.ServeHTTP(rw, r)
33133
}

coderd/workspaceapps_test.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,43 @@
11
package coderd_test
22

33
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net"
8+
"net/http"
49
"testing"
510

11+
"github.com/google/uuid"
12+
"github.com/stretchr/testify/require"
13+
614
"cdr.dev/slog/sloggers/slogtest"
715
"github.com/coder/coder/agent"
816
"github.com/coder/coder/coderd/coderdtest"
917
"github.com/coder/coder/codersdk"
1018
"github.com/coder/coder/provisioner/echo"
1119
"github.com/coder/coder/provisionersdk/proto"
12-
"github.com/google/uuid"
1320
)
1421

1522
func TestWorkspaceAppsProxyPath(t *testing.T) {
1623
t.Parallel()
1724
t.Run("Proxies", func(t *testing.T) {
1825
t.Parallel()
26+
// #nosec
27+
ln, err := net.Listen("tcp", ":0")
28+
require.NoError(t, err)
29+
server := http.Server{
30+
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31+
w.WriteHeader(http.StatusOK)
32+
}),
33+
}
34+
t.Cleanup(func() {
35+
_ = server.Close()
36+
_ = ln.Close()
37+
})
38+
go server.Serve(ln)
39+
tcpAddr, _ := ln.Addr().(*net.TCPAddr)
40+
1941
client, coderAPI := coderdtest.NewWithAPI(t, nil)
2042
user := coderdtest.CreateFirstUser(t, client)
2143
daemonCloser := coderdtest.NewProvisionerDaemon(t, coderAPI)
@@ -34,6 +56,10 @@ func TestWorkspaceAppsProxyPath(t *testing.T) {
3456
Auth: &proto.Agent_Token{
3557
Token: authToken,
3658
},
59+
Apps: []*proto.App{{
60+
Name: "example",
61+
Url: fmt.Sprintf("http://127.0.0.1:%d", tcpAddr.Port),
62+
}},
3763
}},
3864
}},
3965
},
@@ -54,6 +80,13 @@ func TestWorkspaceAppsProxyPath(t *testing.T) {
5480
t.Cleanup(func() {
5581
_ = agentCloser.Close()
5682
})
57-
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
83+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
84+
85+
resp, err := client.Request(context.Background(), http.MethodGet, "/me/"+workspace.Name+"/example", nil)
86+
require.NoError(t, err)
87+
body, err := io.ReadAll(resp.Body)
88+
require.NoError(t, err)
89+
require.Equal(t, "", string(body))
90+
require.Equal(t, http.StatusOK, resp.StatusCode)
5891
})
5992
}

coderd/workspaceresources_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestWorkspaceResource(t *testing.T) {
4646

4747
t.Run("Apps", func(t *testing.T) {
4848
t.Parallel()
49-
_, client, coderd := coderdtest.NewWithServer(t, nil)
49+
client, coderd := coderdtest.NewWithAPI(t, nil)
5050
user := coderdtest.CreateFirstUser(t, client)
5151
coderdtest.NewProvisionerDaemon(t, coderd)
5252
app := &proto.App{

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