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

Skip to content

Commit 9ea7924

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

File tree

11 files changed

+79
-12
lines changed

11 files changed

+79
-12
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;

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
}

enterprise/wsproxy/wsproxysdk/wsproxysdk.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import (
2121
"github.com/coder/websocket"
2222
)
2323

24+
const (
25+
// CoderWorkspaceProxyAuthTokenHeader is the header that contains the
26+
// resolved real IP address of the client that made the request to the proxy.
27+
CoderWorkspaceProxyRealIPHeader = "Coder-Workspace-Proxy-Real-IP"
28+
)
29+
2430
// Client is a HTTP client for a subset of Coder API routes that external
2531
// proxies need.
2632
type Client struct {
@@ -84,10 +90,11 @@ type IssueSignedAppTokenResponse struct {
8490
// IssueSignedAppToken issues a new signed app token for the provided app
8591
// request. The error page will be returned as JSON. For use in external
8692
// proxies, use IssueSignedAppTokenHTML instead.
87-
func (c *Client) IssueSignedAppToken(ctx context.Context, req workspaceapps.IssueTokenRequest) (IssueSignedAppTokenResponse, error) {
93+
func (c *Client) IssueSignedAppToken(ctx context.Context, req workspaceapps.IssueTokenRequest, clientIP string) (IssueSignedAppTokenResponse, error) {
8894
resp, err := c.RequestIgnoreRedirects(ctx, http.MethodPost, "/api/v2/workspaceproxies/me/issue-signed-app-token", req, func(r *http.Request) {
8995
// This forces any HTML error pages to be returned as JSON instead.
9096
r.Header.Set("Accept", "application/json")
97+
r.Header.Set(CoderWorkspaceProxyRealIPHeader, clientIP)
9198
})
9299
if err != nil {
93100
return IssueSignedAppTokenResponse{}, xerrors.Errorf("make request: %w", err)
@@ -105,7 +112,7 @@ func (c *Client) IssueSignedAppToken(ctx context.Context, req workspaceapps.Issu
105112
// IssueSignedAppTokenHTML issues a new signed app token for the provided app
106113
// request. The error page will be returned as HTML in most cases, and will be
107114
// written directly to the provided http.ResponseWriter.
108-
func (c *Client) IssueSignedAppTokenHTML(ctx context.Context, rw http.ResponseWriter, req workspaceapps.IssueTokenRequest) (IssueSignedAppTokenResponse, bool) {
115+
func (c *Client) IssueSignedAppTokenHTML(ctx context.Context, rw http.ResponseWriter, req workspaceapps.IssueTokenRequest, clientIP string) (IssueSignedAppTokenResponse, bool) {
109116
writeError := func(rw http.ResponseWriter, err error) {
110117
res := codersdk.Response{
111118
Message: "Internal server error",
@@ -117,6 +124,7 @@ func (c *Client) IssueSignedAppTokenHTML(ctx context.Context, rw http.ResponseWr
117124

118125
resp, err := c.RequestIgnoreRedirects(ctx, http.MethodPost, "/api/v2/workspaceproxies/me/issue-signed-app-token", req, func(r *http.Request) {
119126
r.Header.Set("Accept", "text/html")
127+
r.Header.Set(CoderWorkspaceProxyRealIPHeader, clientIP)
120128
})
121129
if err != nil {
122130
writeError(rw, xerrors.Errorf("perform issue signed app token request: %w", err))

enterprise/wsproxy/wsproxysdk/wsproxysdk_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
func Test_IssueSignedAppTokenHTML(t *testing.T) {
2323
t.Parallel()
2424

25+
fakeClientIP := "127.0.0.1"
26+
2527
t.Run("OK", func(t *testing.T) {
2628
t.Parallel()
2729

@@ -68,7 +70,7 @@ func Test_IssueSignedAppTokenHTML(t *testing.T) {
6870
tokenRes, ok := client.IssueSignedAppTokenHTML(ctx, rw, workspaceapps.IssueTokenRequest{
6971
AppRequest: expectedAppReq,
7072
SessionToken: expectedSessionToken,
71-
})
73+
}, fakeClientIP)
7274
if !assert.True(t, ok) {
7375
t.Log("issue request failed when it should've succeeded")
7476
t.Log("response dump:")
@@ -118,7 +120,7 @@ func Test_IssueSignedAppTokenHTML(t *testing.T) {
118120
tokenRes, ok := client.IssueSignedAppTokenHTML(ctx, rw, workspaceapps.IssueTokenRequest{
119121
AppRequest: workspaceapps.Request{},
120122
SessionToken: "user-session-token",
121-
})
123+
}, fakeClientIP)
122124
require.False(t, ok)
123125
require.Empty(t, tokenRes)
124126
require.True(t, rw.WasWritten())

0 commit comments

Comments
 (0)