Skip to content

Commit 0c1ea95

Browse files
committed
Merge branch 'main' of https://github.com/coder/coder into bq/support-weekly
2 parents f1ea361 + cb60409 commit 0c1ea95

File tree

73 files changed

+1752
-2563
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1752
-2563
lines changed

.golangci.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ linters-settings:
1010
include:
1111
# Gradually extend to cover more of the codebase.
1212
- 'httpmw\.\w+'
13+
# We want to enforce all values are specified when inserting or updating
14+
# a database row. Ref: #9936
15+
- 'github.com/coder/coder/v2/coderd/database\.[^G][^e][^t]\w+Params'
1316
gocognit:
1417
min-complexity: 300
1518

cli/errors.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import (
66
"net/http/httptest"
77
"os"
88

9+
"golang.org/x/xerrors"
10+
911
"github.com/coder/coder/v2/cli/clibase"
1012
"github.com/coder/coder/v2/codersdk"
11-
"golang.org/x/xerrors"
1213
)
1314

1415
func (RootCmd) errorExample() *clibase.Cmd {

cli/remoteforward.go

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"net"
8+
"os"
89
"regexp"
910
"strconv"
1011

@@ -23,15 +24,24 @@ type cookieAddr struct {
2324

2425
// Format:
2526
// remote_port:local_address:local_port
26-
var remoteForwardRegex = regexp.MustCompile(`^(\d+):(.+):(\d+)$`)
27+
var remoteForwardRegexTCP = regexp.MustCompile(`^(\d+):(.+):(\d+)$`)
2728

28-
func validateRemoteForward(flag string) bool {
29-
return remoteForwardRegex.MatchString(flag)
29+
// remote_socket_path:local_socket_path (both absolute paths)
30+
var remoteForwardRegexUnixSocket = regexp.MustCompile(`^(\/.+):(\/.+)$`)
31+
32+
func isRemoteForwardTCP(flag string) bool {
33+
return remoteForwardRegexTCP.MatchString(flag)
3034
}
3135

32-
func parseRemoteForward(flag string) (net.Addr, net.Addr, error) {
33-
matches := remoteForwardRegex.FindStringSubmatch(flag)
36+
func isRemoteForwardUnixSocket(flag string) bool {
37+
return remoteForwardRegexUnixSocket.MatchString(flag)
38+
}
39+
40+
func validateRemoteForward(flag string) bool {
41+
return isRemoteForwardTCP(flag) || isRemoteForwardUnixSocket(flag)
42+
}
3443

44+
func parseRemoteForwardTCP(matches []string) (net.Addr, net.Addr, error) {
3545
remotePort, err := strconv.Atoi(matches[1])
3646
if err != nil {
3747
return nil, nil, xerrors.Errorf("remote port is invalid: %w", err)
@@ -57,6 +67,46 @@ func parseRemoteForward(flag string) (net.Addr, net.Addr, error) {
5767
return localAddr, remoteAddr, nil
5868
}
5969

70+
func parseRemoteForwardUnixSocket(matches []string) (net.Addr, net.Addr, error) {
71+
remoteSocket := matches[1]
72+
localSocket := matches[2]
73+
74+
fileInfo, err := os.Stat(localSocket)
75+
if err != nil {
76+
return nil, nil, err
77+
}
78+
79+
if fileInfo.Mode()&os.ModeSocket == 0 {
80+
return nil, nil, xerrors.New("File is not a Unix domain socket file")
81+
}
82+
83+
remoteAddr := &net.UnixAddr{
84+
Name: remoteSocket,
85+
Net: "unix",
86+
}
87+
88+
localAddr := &net.UnixAddr{
89+
Name: localSocket,
90+
Net: "unix",
91+
}
92+
return localAddr, remoteAddr, nil
93+
}
94+
95+
func parseRemoteForward(flag string) (net.Addr, net.Addr, error) {
96+
tcpMatches := remoteForwardRegexTCP.FindStringSubmatch(flag)
97+
98+
if len(tcpMatches) > 0 {
99+
return parseRemoteForwardTCP(tcpMatches)
100+
}
101+
102+
unixSocketMatches := remoteForwardRegexUnixSocket.FindStringSubmatch(flag)
103+
if len(unixSocketMatches) > 0 {
104+
return parseRemoteForwardUnixSocket(unixSocketMatches)
105+
}
106+
107+
return nil, nil, xerrors.New("Could not match forward arguments")
108+
}
109+
60110
// sshRemoteForward starts forwarding connections from a remote listener to a
61111
// local address via SSH in a goroutine.
62112
//

cli/ssh_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,54 @@ func TestSSH(t *testing.T) {
428428
<-cmdDone
429429
})
430430

431+
t.Run("RemoteForwardUnixSocket", func(t *testing.T) {
432+
if runtime.GOOS == "windows" {
433+
t.Skip("Test not supported on windows")
434+
}
435+
436+
t.Parallel()
437+
438+
client, workspace, agentToken := setupWorkspaceForAgent(t, nil)
439+
440+
_ = agenttest.New(t, client.URL, agentToken)
441+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
442+
443+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
444+
defer cancel()
445+
446+
tmpdir := tempDirUnixSocket(t)
447+
agentSock := filepath.Join(tmpdir, "agent.sock")
448+
l, err := net.Listen("unix", agentSock)
449+
require.NoError(t, err)
450+
defer l.Close()
451+
452+
inv, root := clitest.New(t,
453+
"ssh",
454+
workspace.Name,
455+
"--remote-forward",
456+
"/tmp/test.sock:"+agentSock,
457+
)
458+
clitest.SetupConfig(t, client, root)
459+
pty := ptytest.New(t).Attach(inv)
460+
inv.Stderr = pty.Output()
461+
cmdDone := tGo(t, func() {
462+
err := inv.WithContext(ctx).Run()
463+
assert.NoError(t, err, "ssh command failed")
464+
})
465+
466+
// Wait for the prompt or any output really to indicate the command has
467+
// started and accepting input on stdin.
468+
_ = pty.Peek(ctx, 1)
469+
470+
// Download the test page
471+
pty.WriteLine("ss -xl state listening src /tmp/test.sock | wc -l")
472+
pty.ExpectMatch("2")
473+
474+
// And we're done.
475+
pty.WriteLine("exit")
476+
<-cmdDone
477+
})
478+
431479
t.Run("FileLogging", func(t *testing.T) {
432480
t.Parallel()
433481

coderd/apikey/apikey.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func Generate(params CreateParams) (database.InsertAPIKeyParams, string, error)
7676
return database.InsertAPIKeyParams{
7777
ID: keyID,
7878
UserID: params.UserID,
79+
LastUsed: time.Time{},
7980
LifetimeSeconds: params.LifetimeSeconds,
8081
IPAddress: pqtype.Inet{
8182
IPNet: net.IPNet{

coderd/audit.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
154154
Diff: diff,
155155
StatusCode: http.StatusOK,
156156
AdditionalFields: params.AdditionalFields,
157+
RequestID: uuid.Nil, // no request ID to attach this to
158+
ResourceIcon: "",
159+
OrganizationID: uuid.New(),
157160
})
158161
if err != nil {
159162
httpapi.InternalServerError(rw, err)

coderd/coderdtest/oidctest/helper.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package oidctest
22

33
import (
4+
"database/sql"
45
"net/http"
56
"testing"
67
"time"
@@ -69,11 +70,13 @@ func (*LoginHelper) ExpireOauthToken(t *testing.T, db database.Store, user *code
6970

7071
// Expire the oauth link for the given user.
7172
updated, err := db.UpdateUserLink(ctx, database.UpdateUserLinkParams{
72-
OAuthAccessToken: link.OAuthAccessToken,
73-
OAuthRefreshToken: link.OAuthRefreshToken,
74-
OAuthExpiry: time.Now().Add(time.Hour * -1),
75-
UserID: link.UserID,
76-
LoginType: link.LoginType,
73+
OAuthAccessToken: link.OAuthAccessToken,
74+
OAuthAccessTokenKeyID: sql.NullString{}, // dbcrypt will update as required
75+
OAuthRefreshToken: link.OAuthRefreshToken,
76+
OAuthRefreshTokenKeyID: sql.NullString{}, // dbcrypt will update as required
77+
OAuthExpiry: time.Now().Add(time.Hour * -1),
78+
UserID: link.UserID,
79+
LoginType: link.LoginType,
7780
})
7881
require.NoError(t, err, "expire user link")
7982

coderd/database/dbfake/dbfake.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4142,6 +4142,8 @@ func (q *FakeQuerier) InsertAllUsersGroup(ctx context.Context, orgID uuid.UUID)
41424142
Name: database.EveryoneGroup,
41434143
DisplayName: "",
41444144
OrganizationID: orgID,
4145+
AvatarURL: "",
4146+
QuotaAllowance: 0,
41454147
})
41464148
}
41474149

coderd/database/dbgen/dbgen.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ func APIKey(t testing.TB, db database.Store, seed database.APIKey) (key database
125125
}
126126

127127
func WorkspaceAgent(t testing.TB, db database.Store, orig database.WorkspaceAgent) database.WorkspaceAgent {
128-
workspace, err := db.InsertWorkspaceAgent(genCtx, database.InsertWorkspaceAgentParams{
128+
agt, err := db.InsertWorkspaceAgent(genCtx, database.InsertWorkspaceAgentParams{
129129
ID: takeFirst(orig.ID, uuid.New()),
130130
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
131131
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
@@ -154,9 +154,10 @@ func WorkspaceAgent(t testing.TB, db database.Store, orig database.WorkspaceAgen
154154
ConnectionTimeoutSeconds: takeFirst(orig.ConnectionTimeoutSeconds, 3600),
155155
TroubleshootingURL: takeFirst(orig.TroubleshootingURL, "https://example.com"),
156156
MOTDFile: takeFirst(orig.TroubleshootingURL, ""),
157+
DisplayApps: append([]database.DisplayApp{}, orig.DisplayApps...),
157158
})
158159
require.NoError(t, err, "insert workspace agent")
159-
return workspace
160+
return agt
160161
}
161162

162163
func Workspace(t testing.TB, db database.Store, orig database.Workspace) database.Workspace {
@@ -204,6 +205,7 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil
204205
JobID: takeFirst(orig.JobID, uuid.New()),
205206
ProvisionerState: takeFirstSlice(orig.ProvisionerState, []byte{}),
206207
Deadline: takeFirst(orig.Deadline, dbtime.Now().Add(time.Hour)),
208+
MaxDeadline: takeFirst(orig.MaxDeadline, time.Time{}),
207209
Reason: takeFirst(orig.Reason, database.BuildReasonInitiator),
208210
})
209211
if err != nil {
@@ -348,6 +350,7 @@ func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig data
348350
Type: takeFirst(orig.Type, database.ProvisionerJobTypeWorkspaceBuild),
349351
Input: takeFirstSlice(orig.Input, []byte("{}")),
350352
Tags: orig.Tags,
353+
TraceMetadata: pqtype.NullRawMessage{},
351354
})
352355
require.NoError(t, err, "insert job")
353356
if ps != nil {
@@ -359,6 +362,7 @@ func ProvisionerJob(t testing.TB, db database.Store, ps pubsub.Pubsub, orig data
359362
StartedAt: orig.StartedAt,
360363
Types: []database.ProvisionerType{database.ProvisionerTypeEcho},
361364
Tags: must(json.Marshal(orig.Tags)),
365+
WorkerID: uuid.NullUUID{},
362366
})
363367
require.NoError(t, err)
364368
}
@@ -460,6 +464,8 @@ func WorkspaceProxy(t testing.TB, db database.Store, orig database.WorkspaceProx
460464
TokenHashedSecret: hashedSecret[:],
461465
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
462466
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
467+
DerpEnabled: takeFirst(orig.DerpEnabled, false),
468+
DerpOnly: takeFirst(orig.DerpEnabled, false),
463469
})
464470
require.NoError(t, err, "insert proxy")
465471

coderd/database/dump.sql

Lines changed: 4 additions & 0 deletions
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+
DROP INDEX workspace_agent_stats_template_id_created_at_user_id_idx;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CREATE INDEX workspace_agent_stats_template_id_created_at_user_id_idx ON workspace_agent_stats USING btree (template_id, created_at DESC, user_id) WHERE connection_count > 0;
2+
3+
COMMENT ON INDEX workspace_agent_stats_template_id_created_at_user_id_idx IS 'Support index for template insights endpoint to build interval reports faster.';

coderd/externalauth.go

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,15 @@ func (api *API) postExternalAuthDeviceByID(rw http.ResponseWriter, r *http.Reque
123123
}
124124

125125
_, err = api.Database.InsertExternalAuthLink(ctx, database.InsertExternalAuthLinkParams{
126-
ProviderID: config.ID,
127-
UserID: apiKey.UserID,
128-
CreatedAt: dbtime.Now(),
129-
UpdatedAt: dbtime.Now(),
130-
OAuthAccessToken: token.AccessToken,
131-
OAuthRefreshToken: token.RefreshToken,
132-
OAuthExpiry: token.Expiry,
126+
ProviderID: config.ID,
127+
UserID: apiKey.UserID,
128+
CreatedAt: dbtime.Now(),
129+
UpdatedAt: dbtime.Now(),
130+
OAuthAccessToken: token.AccessToken,
131+
OAuthAccessTokenKeyID: sql.NullString{}, // dbcrypt will set as required
132+
OAuthRefreshToken: token.RefreshToken,
133+
OAuthRefreshTokenKeyID: sql.NullString{}, // dbcrypt will set as required
134+
OAuthExpiry: token.Expiry,
133135
})
134136
if err != nil {
135137
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@@ -140,12 +142,14 @@ func (api *API) postExternalAuthDeviceByID(rw http.ResponseWriter, r *http.Reque
140142
}
141143
} else {
142144
_, err = api.Database.UpdateExternalAuthLink(ctx, database.UpdateExternalAuthLinkParams{
143-
ProviderID: config.ID,
144-
UserID: apiKey.UserID,
145-
UpdatedAt: dbtime.Now(),
146-
OAuthAccessToken: token.AccessToken,
147-
OAuthRefreshToken: token.RefreshToken,
148-
OAuthExpiry: token.Expiry,
145+
ProviderID: config.ID,
146+
UserID: apiKey.UserID,
147+
UpdatedAt: dbtime.Now(),
148+
OAuthAccessToken: token.AccessToken,
149+
OAuthAccessTokenKeyID: sql.NullString{}, // dbcrypt will update as required
150+
OAuthRefreshToken: token.RefreshToken,
151+
OAuthRefreshTokenKeyID: sql.NullString{}, // dbcrypt will update as required
152+
OAuthExpiry: token.Expiry,
149153
})
150154
if err != nil {
151155
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@@ -211,13 +215,15 @@ func (api *API) externalAuthCallback(externalAuthConfig *externalauth.Config) ht
211215
}
212216

213217
_, err = api.Database.InsertExternalAuthLink(ctx, database.InsertExternalAuthLinkParams{
214-
ProviderID: externalAuthConfig.ID,
215-
UserID: apiKey.UserID,
216-
CreatedAt: dbtime.Now(),
217-
UpdatedAt: dbtime.Now(),
218-
OAuthAccessToken: state.Token.AccessToken,
219-
OAuthRefreshToken: state.Token.RefreshToken,
220-
OAuthExpiry: state.Token.Expiry,
218+
ProviderID: externalAuthConfig.ID,
219+
UserID: apiKey.UserID,
220+
CreatedAt: dbtime.Now(),
221+
UpdatedAt: dbtime.Now(),
222+
OAuthAccessToken: state.Token.AccessToken,
223+
OAuthAccessTokenKeyID: sql.NullString{}, // dbcrypt will set as required
224+
OAuthRefreshToken: state.Token.RefreshToken,
225+
OAuthRefreshTokenKeyID: sql.NullString{}, // dbcrypt will set as required
226+
OAuthExpiry: state.Token.Expiry,
221227
})
222228
if err != nil {
223229
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@@ -228,12 +234,14 @@ func (api *API) externalAuthCallback(externalAuthConfig *externalauth.Config) ht
228234
}
229235
} else {
230236
_, err = api.Database.UpdateExternalAuthLink(ctx, database.UpdateExternalAuthLinkParams{
231-
ProviderID: externalAuthConfig.ID,
232-
UserID: apiKey.UserID,
233-
UpdatedAt: dbtime.Now(),
234-
OAuthAccessToken: state.Token.AccessToken,
235-
OAuthRefreshToken: state.Token.RefreshToken,
236-
OAuthExpiry: state.Token.Expiry,
237+
ProviderID: externalAuthConfig.ID,
238+
UserID: apiKey.UserID,
239+
UpdatedAt: dbtime.Now(),
240+
OAuthAccessToken: state.Token.AccessToken,
241+
OAuthAccessTokenKeyID: sql.NullString{}, // dbcrypt will update as required
242+
OAuthRefreshToken: state.Token.RefreshToken,
243+
OAuthRefreshTokenKeyID: sql.NullString{}, // dbcrypt will update as required
244+
OAuthExpiry: state.Token.Expiry,
237245
})
238246
if err != nil {
239247
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{

0 commit comments

Comments
 (0)