Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dd9ae88

Browse files
committed
fix!: use client ip when creating connection logs for workspace proxied app accesses
1 parent 6d39077 commit dd9ae88

File tree

12 files changed

+99
-11
lines changed

12 files changed

+99
-11
lines changed

coderd/database/dump.sql

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE connection_logs ALTER COLUMN ip SET NOT NULL;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- We can't guarantee that an IP will always be available, and omitting an IP
2+
-- is preferable to not creating a connection log at all.
3+
ALTER TABLE connection_logs ALTER COLUMN ip DROP NOT NULL;

coderd/workspaceapps/errors.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ func WriteWorkspaceApp404(log slog.Logger, accessURL *url.URL, rw http.ResponseW
3939
})
4040
}
4141

42+
// WriteWorkspaceApp401 writes a generic authentication required HTML 401 error
43+
// page for a workspace app.
44+
func WriteWorkspaceApp401(accessURL *url.URL, rw http.ResponseWriter, r *http.Request) {
45+
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
46+
Status: http.StatusUnauthorized,
47+
Title: "Unauthorized",
48+
Description: "Authentication required",
49+
RetryEnabled: false,
50+
DashboardURL: accessURL.String(),
51+
})
52+
}
53+
4254
// WriteWorkspaceApp500 writes a HTML 500 error page for a workspace app. If
4355
// appReq is not nil, it's fields will be added to the logged error message.
4456
func WriteWorkspaceApp500(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, appReq *Request, err error, msg string) {

coderd/workspaceapps/provider.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ func ResolveRequest(rw http.ResponseWriter, r *http.Request, opts ResolveRequest
5454
return token, true
5555
}
5656

57+
if appReq.AccessMethod == AccessMethodTerminal {
58+
// The client is expected to include the signed token in a query parameter
59+
// in the request. If that token doesn't exist or isn't valid then the
60+
// issuing code below won't work on proxies because there is no long
61+
// lived session token on the root proxy domain.
62+
WriteWorkspaceApp401(opts.DashboardURL, rw, r)
63+
return nil, false
64+
}
65+
5766
issueReq := IssueTokenRequest{
5867
AppRequest: appReq,
5968
PathAppBaseURL: opts.PathAppBaseURL.String(),

codersdk/connectionlog.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type ConnectionLog struct {
2020
WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"`
2121
WorkspaceName string `json:"workspace_name"`
2222
AgentName string `json:"agent_name"`
23-
IP netip.Addr `json:"ip"`
23+
IP *netip.Addr `json:"ip,omitempty"`
2424
Type ConnectionType `json:"type"`
2525

2626
// WebInfo is only set when `type` is one of:

enterprise/coderd/connectionlog.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,13 @@ func convertConnectionLogs(dblogs []database.GetConnectionLogsOffsetRow) []coder
9393
}
9494

9595
func convertConnectionLog(dblog database.GetConnectionLogsOffsetRow) codersdk.ConnectionLog {
96-
ip, _ := netip.AddrFromSlice(dblog.ConnectionLog.Ip.IPNet.IP)
96+
var ip *netip.Addr
97+
if dblog.ConnectionLog.Ip.Valid {
98+
parsedIP, ok := netip.AddrFromSlice(dblog.ConnectionLog.Ip.IPNet.IP)
99+
if ok {
100+
ip = &parsedIP
101+
}
102+
}
97103

98104
var user *codersdk.User
99105
if dblog.ConnectionLog.UserID.Valid {

enterprise/coderd/workspaceproxy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ func (api *API) workspaceProxyIssueSignedAppToken(rw http.ResponseWriter, r *htt
490490
return
491491
}
492492
userReq.Header.Set(codersdk.SessionTokenHeader, req.SessionToken)
493+
userReq.RemoteAddr = r.Header.Get(wsproxysdk.CoderWorkspaceProxyRealIPHeader)
493494

494495
// Exchange the token.
495496
token, tokenStr, ok := api.AGPL.WorkspaceAppsProvider.Issue(ctx, rw, userReq, req)

enterprise/coderd/workspaceproxy_test.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package coderd_test
33
import (
44
"database/sql"
55
"fmt"
6+
"net"
67
"net/http"
78
"net/http/httptest"
89
"net/http/httputil"
@@ -12,13 +13,15 @@ import (
1213
"time"
1314

1415
"github.com/google/uuid"
16+
"github.com/sqlc-dev/pqtype"
1517
"github.com/stretchr/testify/assert"
1618
"github.com/stretchr/testify/require"
1719

1820
"cdr.dev/slog/sloggers/slogtest"
1921
"github.com/coder/coder/v2/agent/agenttest"
2022
"github.com/coder/coder/v2/buildinfo"
2123
"github.com/coder/coder/v2/coderd/coderdtest"
24+
"github.com/coder/coder/v2/coderd/connectionlog"
2225
"github.com/coder/coder/v2/coderd/database"
2326
"github.com/coder/coder/v2/coderd/database/db2sdk"
2427
"github.com/coder/coder/v2/coderd/database/dbgen"
@@ -610,13 +613,18 @@ func TestProxyRegisterDeregister(t *testing.T) {
610613
func TestIssueSignedAppToken(t *testing.T) {
611614
t.Parallel()
612615

616+
connectionLogger := connectionlog.NewFake()
617+
613618
client, user := coderdenttest.New(t, &coderdenttest.Options{
619+
ConnectionLogging: true,
614620
Options: &coderdtest.Options{
615621
IncludeProvisionerDaemon: true,
622+
ConnectionLogger: connectionLogger,
616623
},
617624
LicenseOptions: &coderdenttest.LicenseOptions{
618625
Features: license.Features{
619626
codersdk.FeatureWorkspaceProxy: 1,
627+
codersdk.FeatureConnectionLog: 1,
620628
},
621629
},
622630
})
@@ -653,7 +661,7 @@ func TestIssueSignedAppToken(t *testing.T) {
653661
// Invalid request.
654662
AppRequest: workspaceapps.Request{},
655663
SessionToken: client.SessionToken(),
656-
})
664+
}, "127.0.0.1")
657665
require.Error(t, err)
658666
})
659667

@@ -669,41 +677,70 @@ func TestIssueSignedAppToken(t *testing.T) {
669677
t.Parallel()
670678
proxyClient := wsproxysdk.New(client.URL, proxyRes.ProxyToken)
671679

680+
fakeClientIP := "13.37.13.37"
681+
parsedFakeClientIP := pqtype.Inet{
682+
Valid: true, IPNet: net.IPNet{
683+
IP: net.ParseIP(fakeClientIP),
684+
Mask: net.CIDRMask(32, 32),
685+
},
686+
}
687+
672688
ctx := testutil.Context(t, testutil.WaitLong)
673-
_, err := proxyClient.IssueSignedAppToken(ctx, goodRequest)
689+
_, err := proxyClient.IssueSignedAppToken(ctx, goodRequest, fakeClientIP)
674690
require.NoError(t, err)
691+
692+
require.True(t, connectionLogger.Contains(t, database.UpsertConnectionLogParams{
693+
Ip: parsedFakeClientIP,
694+
}))
675695
})
676696

677697
t.Run("OKHTML", func(t *testing.T) {
678698
t.Parallel()
679699
proxyClient := wsproxysdk.New(client.URL, proxyRes.ProxyToken)
680700

701+
fakeClientIP := "192.168.1.100"
702+
parsedFakeClientIP := pqtype.Inet{
703+
Valid: true, IPNet: net.IPNet{
704+
IP: net.ParseIP(fakeClientIP),
705+
Mask: net.CIDRMask(32, 32),
706+
},
707+
}
708+
681709
rw := httptest.NewRecorder()
682710
ctx := testutil.Context(t, testutil.WaitLong)
683-
_, ok := proxyClient.IssueSignedAppTokenHTML(ctx, rw, goodRequest)
711+
_, ok := proxyClient.IssueSignedAppTokenHTML(ctx, rw, goodRequest, fakeClientIP)
684712
if !assert.True(t, ok, "expected true") {
685713
resp := rw.Result()
686714
defer resp.Body.Close()
687715
dump, err := httputil.DumpResponse(resp, true)
688716
require.NoError(t, err)
689717
t.Log(string(dump))
690718
}
719+
720+
require.True(t, connectionLogger.Contains(t, database.UpsertConnectionLogParams{
721+
Ip: parsedFakeClientIP,
722+
}))
691723
})
692724
}
693725

694726
func TestReconnectingPTYSignedToken(t *testing.T) {
695727
t.Parallel()
696728

729+
connectionLogger := connectionlog.NewFake()
730+
697731
db, pubsub := dbtestutil.NewDB(t)
698732
client, closer, api, user := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
733+
ConnectionLogging: true,
699734
Options: &coderdtest.Options{
700735
Database: db,
701736
Pubsub: pubsub,
702737
IncludeProvisionerDaemon: true,
738+
ConnectionLogger: connectionLogger,
703739
},
704740
LicenseOptions: &coderdenttest.LicenseOptions{
705741
Features: license.Features{
706742
codersdk.FeatureWorkspaceProxy: 1,
743+
codersdk.FeatureConnectionLog: 1,
707744
},
708745
},
709746
})
@@ -887,6 +924,15 @@ func TestReconnectingPTYSignedToken(t *testing.T) {
887924

888925
// The token is validated in the apptest suite, so we don't need to
889926
// validate it here.
927+
928+
require.True(t, connectionLogger.Contains(t, database.UpsertConnectionLogParams{
929+
Ip: pqtype.Inet{
930+
Valid: true, IPNet: net.IPNet{
931+
IP: net.ParseIP("127.0.0.1"),
932+
Mask: net.CIDRMask(32, 32),
933+
},
934+
},
935+
}))
890936
})
891937
}
892938

enterprise/wsproxy/tokenprovider.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func (p *TokenProvider) Issue(ctx context.Context, rw http.ResponseWriter, r *ht
3939
}
4040
issueReq.AppRequest = appReq
4141

42-
resp, ok := p.Client.IssueSignedAppTokenHTML(ctx, rw, issueReq)
42+
resp, ok := p.Client.IssueSignedAppTokenHTML(ctx, rw, issueReq, r.RemoteAddr)
4343
if !ok {
4444
return nil, "", false
4545
}

0 commit comments

Comments
 (0)