Skip to content

Commit 8958b64

Browse files
authored
feat: Add agent authentication based on instance ID (#336)
* feat: Add agent authentication based on instance ID Each cloud has it's own unique instance identity signatures, which can be used for zero-token authentication. This change adds support for tracking by "instance_id", and automatically authenticating with Google Cloud. * Add test for CLI * Fix workspace agent request name * Fix race with adding to wait group * Fix name of instance identity token
1 parent 67613da commit 8958b64

Some content is hidden

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

41 files changed

+752
-251
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@
3636
"gossh",
3737
"hashicorp",
3838
"httpmw",
39+
"idtoken",
3940
"isatty",
4041
"Jobf",
4142
"kirsle",
4243
"manifoldco",
4344
"mattn",
45+
"mitchellh",
4446
"moby",
4547
"nhooyr",
4648
"nolint",

cli/clitest/clitest_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func TestMain(m *testing.M) {
1818
func TestCli(t *testing.T) {
1919
t.Parallel()
2020
clitest.CreateProjectVersionSource(t, nil)
21-
client := coderdtest.New(t)
21+
client := coderdtest.New(t, nil)
2222
cmd, config := clitest.New(t)
2323
clitest.SetupConfig(t, client, config)
2424
pty := ptytest.New(t)

cli/login_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ func TestLogin(t *testing.T) {
1616
t.Parallel()
1717
t.Run("InitialUserNoTTY", func(t *testing.T) {
1818
t.Parallel()
19-
client := coderdtest.New(t)
19+
client := coderdtest.New(t, nil)
2020
root, _ := clitest.New(t, "login", client.URL.String())
2121
err := root.Execute()
2222
require.Error(t, err)
2323
})
2424

2525
t.Run("InitialUserTTY", func(t *testing.T) {
2626
t.Parallel()
27-
client := coderdtest.New(t)
27+
client := coderdtest.New(t, nil)
2828
// The --force-tty flag is required on Windows, because the `isatty` library does not
2929
// accurately detect Windows ptys when they are not attached to a process:
3030
// https://github.com/mattn/go-isatty/issues/59
@@ -55,7 +55,7 @@ func TestLogin(t *testing.T) {
5555

5656
t.Run("ExistingUserValidTokenTTY", func(t *testing.T) {
5757
t.Parallel()
58-
client := coderdtest.New(t)
58+
client := coderdtest.New(t, nil)
5959
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
6060
Username: "test-user",
6161
Email: "test-user@coder.com",
@@ -85,7 +85,7 @@ func TestLogin(t *testing.T) {
8585

8686
t.Run("ExistingUserInvalidTokenTTY", func(t *testing.T) {
8787
t.Parallel()
88-
client := coderdtest.New(t)
88+
client := coderdtest.New(t, nil)
8989
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
9090
Username: "test-user",
9191
Email: "test-user@coder.com",

cli/projectcreate_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func TestProjectCreate(t *testing.T) {
1717
t.Parallel()
1818
t.Run("NoParameters", func(t *testing.T) {
1919
t.Parallel()
20-
client := coderdtest.New(t)
20+
client := coderdtest.New(t, nil)
2121
coderdtest.CreateInitialUser(t, client)
2222
source := clitest.CreateProjectVersionSource(t, &echo.Responses{
2323
Parse: echo.ParseComplete,
@@ -53,7 +53,7 @@ func TestProjectCreate(t *testing.T) {
5353

5454
t.Run("Parameter", func(t *testing.T) {
5555
t.Parallel()
56-
client := coderdtest.New(t)
56+
client := coderdtest.New(t, nil)
5757
coderdtest.CreateInitialUser(t, client)
5858
source := clitest.CreateProjectVersionSource(t, &echo.Responses{
5959
Parse: []*proto.Parse_Response{{

cli/projectlist_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func TestProjectList(t *testing.T) {
1414
t.Parallel()
1515
t.Run("None", func(t *testing.T) {
1616
t.Parallel()
17-
client := coderdtest.New(t)
17+
client := coderdtest.New(t, nil)
1818
coderdtest.CreateInitialUser(t, client)
1919
cmd, root := clitest.New(t, "projects", "list")
2020
clitest.SetupConfig(t, client, root)
@@ -32,7 +32,7 @@ func TestProjectList(t *testing.T) {
3232
})
3333
t.Run("List", func(t *testing.T) {
3434
t.Parallel()
35-
client := coderdtest.New(t)
35+
client := coderdtest.New(t, nil)
3636
user := coderdtest.CreateInitialUser(t, client)
3737
daemon := coderdtest.NewProvisionerDaemon(t, client)
3838
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)

cli/workspacecreate_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func TestWorkspaceCreate(t *testing.T) {
1616
t.Parallel()
1717
t.Run("Create", func(t *testing.T) {
1818
t.Parallel()
19-
client := coderdtest.New(t)
19+
client := coderdtest.New(t, nil)
2020
user := coderdtest.CreateInitialUser(t, client)
2121
_ = coderdtest.NewProvisionerDaemon(t, client)
2222
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{

coderd/coderd.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"sync"
66

77
"github.com/go-chi/chi/v5"
8+
"google.golang.org/api/idtoken"
89

910
"cdr.dev/slog"
1011
"github.com/coder/coder/database"
@@ -18,6 +19,8 @@ type Options struct {
1819
Logger slog.Logger
1920
Database database.Store
2021
Pubsub database.Pubsub
22+
23+
GoogleTokenValidator *idtoken.Validator
2124
}
2225

2326
// New constructs the Coder API into an HTTP handler.
@@ -107,6 +110,12 @@ func New(options *Options) (http.Handler, func()) {
107110
})
108111
})
109112

113+
r.Route("/workspaceagent", func(r chi.Router) {
114+
r.Route("/authenticate", func(r chi.Router) {
115+
r.Post("/google-instance-identity", api.postAuthenticateWorkspaceAgentUsingGoogleInstanceIdentity)
116+
})
117+
})
118+
110119
r.Route("/files", func(r chi.Router) {
111120
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
112121
r.Post("/", api.postFiles)

coderd/coderdtest/coderdtest.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
"github.com/google/uuid"
1616
"github.com/moby/moby/pkg/namesgenerator"
1717
"github.com/stretchr/testify/require"
18+
"go.opencensus.io/stats/view"
19+
"google.golang.org/api/idtoken"
20+
"google.golang.org/api/option"
1821

1922
"cdr.dev/slog"
2023
"cdr.dev/slog/sloggers/slogtest"
@@ -29,9 +32,31 @@ import (
2932
"github.com/coder/coder/provisionersdk/proto"
3033
)
3134

35+
type Options struct {
36+
GoogleTokenValidator *idtoken.Validator
37+
}
38+
3239
// New constructs an in-memory coderd instance and returns
3340
// the connected client.
34-
func New(t *testing.T) *codersdk.Client {
41+
func New(t *testing.T, options *Options) *codersdk.Client {
42+
// Stops the opencensus.io worker from leaking a goroutine.
43+
// The worker isn't used anyways, and is an indirect dependency
44+
// of the Google Cloud SDK.
45+
t.Cleanup(func() {
46+
view.Stop()
47+
})
48+
49+
if options == nil {
50+
options = &Options{}
51+
}
52+
if options.GoogleTokenValidator == nil {
53+
ctx, cancelFunc := context.WithCancel(context.Background())
54+
t.Cleanup(cancelFunc)
55+
var err error
56+
options.GoogleTokenValidator, err = idtoken.NewValidator(ctx, option.WithoutAuthentication())
57+
require.NoError(t, err)
58+
}
59+
3560
// This can be hotswapped for a live database instance.
3661
db := databasefake.New()
3762
pubsub := database.NewPubsubInMemory()
@@ -59,6 +84,8 @@ func New(t *testing.T) *codersdk.Client {
5984
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
6085
Database: db,
6186
Pubsub: pubsub,
87+
88+
GoogleTokenValidator: options.GoogleTokenValidator,
6289
})
6390
srv := httptest.NewUnstartedServer(handler)
6491
srv.Config.BaseContext = func(_ net.Listener) context.Context {

coderd/coderdtest/coderdtest_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestMain(m *testing.M) {
1919

2020
func TestNew(t *testing.T) {
2121
t.Parallel()
22-
client := coderdtest.New(t)
22+
client := coderdtest.New(t, nil)
2323
user := coderdtest.CreateInitialUser(t, client)
2424
closer := coderdtest.NewProvisionerDaemon(t, client)
2525
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)

coderd/files_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@ func TestPostFiles(t *testing.T) {
1414
t.Parallel()
1515
t.Run("BadContentType", func(t *testing.T) {
1616
t.Parallel()
17-
client := coderdtest.New(t)
17+
client := coderdtest.New(t, nil)
1818
_ = coderdtest.CreateInitialUser(t, client)
1919
_, err := client.UploadFile(context.Background(), "bad", []byte{'a'})
2020
require.Error(t, err)
2121
})
2222

2323
t.Run("Insert", func(t *testing.T) {
2424
t.Parallel()
25-
client := coderdtest.New(t)
25+
client := coderdtest.New(t, nil)
2626
_ = coderdtest.CreateInitialUser(t, client)
2727
_, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, make([]byte, 1024))
2828
require.NoError(t, err)
2929
})
3030

3131
t.Run("InsertAlreadyExists", func(t *testing.T) {
3232
t.Parallel()
33-
client := coderdtest.New(t)
33+
client := coderdtest.New(t, nil)
3434
_ = coderdtest.CreateInitialUser(t, client)
3535
data := make([]byte, 1024)
3636
_, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data)

0 commit comments

Comments
 (0)