Skip to content

Commit

Permalink
feat: Add agent authentication based on instance ID (#336)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kylecarbs committed Feb 21, 2022
1 parent 67613da commit 8958b64
Show file tree
Hide file tree
Showing 41 changed files with 752 additions and 251 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@
"gossh",
"hashicorp",
"httpmw",
"idtoken",
"isatty",
"Jobf",
"kirsle",
"manifoldco",
"mattn",
"mitchellh",
"moby",
"nhooyr",
"nolint",
Expand Down
2 changes: 1 addition & 1 deletion cli/clitest/clitest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestMain(m *testing.M) {
func TestCli(t *testing.T) {
t.Parallel()
clitest.CreateProjectVersionSource(t, nil)
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
cmd, config := clitest.New(t)
clitest.SetupConfig(t, client, config)
pty := ptytest.New(t)
Expand Down
8 changes: 4 additions & 4 deletions cli/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ func TestLogin(t *testing.T) {
t.Parallel()
t.Run("InitialUserNoTTY", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
root, _ := clitest.New(t, "login", client.URL.String())
err := root.Execute()
require.Error(t, err)
})

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

t.Run("ExistingUserValidTokenTTY", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
Username: "test-user",
Email: "test-user@coder.com",
Expand Down Expand Up @@ -85,7 +85,7 @@ func TestLogin(t *testing.T) {

t.Run("ExistingUserInvalidTokenTTY", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
Username: "test-user",
Email: "test-user@coder.com",
Expand Down
4 changes: 2 additions & 2 deletions cli/projectcreate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func TestProjectCreate(t *testing.T) {
t.Parallel()
t.Run("NoParameters", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
coderdtest.CreateInitialUser(t, client)
source := clitest.CreateProjectVersionSource(t, &echo.Responses{
Parse: echo.ParseComplete,
Expand Down Expand Up @@ -53,7 +53,7 @@ func TestProjectCreate(t *testing.T) {

t.Run("Parameter", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
coderdtest.CreateInitialUser(t, client)
source := clitest.CreateProjectVersionSource(t, &echo.Responses{
Parse: []*proto.Parse_Response{{
Expand Down
4 changes: 2 additions & 2 deletions cli/projectlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestProjectList(t *testing.T) {
t.Parallel()
t.Run("None", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
coderdtest.CreateInitialUser(t, client)
cmd, root := clitest.New(t, "projects", "list")
clitest.SetupConfig(t, client, root)
Expand All @@ -32,7 +32,7 @@ func TestProjectList(t *testing.T) {
})
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
daemon := coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
Expand Down
2 changes: 1 addition & 1 deletion cli/workspacecreate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestWorkspaceCreate(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
_ = coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{
Expand Down
9 changes: 9 additions & 0 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"sync"

"github.com/go-chi/chi/v5"
"google.golang.org/api/idtoken"

"cdr.dev/slog"
"github.com/coder/coder/database"
Expand All @@ -18,6 +19,8 @@ type Options struct {
Logger slog.Logger
Database database.Store
Pubsub database.Pubsub

GoogleTokenValidator *idtoken.Validator
}

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

r.Route("/workspaceagent", func(r chi.Router) {
r.Route("/authenticate", func(r chi.Router) {
r.Post("/google-instance-identity", api.postAuthenticateWorkspaceAgentUsingGoogleInstanceIdentity)
})
})

r.Route("/files", func(r chi.Router) {
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
r.Post("/", api.postFiles)
Expand Down
29 changes: 28 additions & 1 deletion coderd/coderdtest/coderdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
"github.com/google/uuid"
"github.com/moby/moby/pkg/namesgenerator"
"github.com/stretchr/testify/require"
"go.opencensus.io/stats/view"
"google.golang.org/api/idtoken"
"google.golang.org/api/option"

"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
Expand All @@ -29,9 +32,31 @@ import (
"github.com/coder/coder/provisionersdk/proto"
)

type Options struct {
GoogleTokenValidator *idtoken.Validator
}

// New constructs an in-memory coderd instance and returns
// the connected client.
func New(t *testing.T) *codersdk.Client {
func New(t *testing.T, options *Options) *codersdk.Client {
// Stops the opencensus.io worker from leaking a goroutine.
// The worker isn't used anyways, and is an indirect dependency
// of the Google Cloud SDK.
t.Cleanup(func() {
view.Stop()
})

if options == nil {
options = &Options{}
}
if options.GoogleTokenValidator == nil {
ctx, cancelFunc := context.WithCancel(context.Background())
t.Cleanup(cancelFunc)
var err error
options.GoogleTokenValidator, err = idtoken.NewValidator(ctx, option.WithoutAuthentication())
require.NoError(t, err)
}

// This can be hotswapped for a live database instance.
db := databasefake.New()
pubsub := database.NewPubsubInMemory()
Expand Down Expand Up @@ -59,6 +84,8 @@ func New(t *testing.T) *codersdk.Client {
Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug),
Database: db,
Pubsub: pubsub,

GoogleTokenValidator: options.GoogleTokenValidator,
})
srv := httptest.NewUnstartedServer(handler)
srv.Config.BaseContext = func(_ net.Listener) context.Context {
Expand Down
2 changes: 1 addition & 1 deletion coderd/coderdtest/coderdtest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestMain(m *testing.M) {

func TestNew(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
closer := coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
Expand Down
6 changes: 3 additions & 3 deletions coderd/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@ func TestPostFiles(t *testing.T) {
t.Parallel()
t.Run("BadContentType", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.UploadFile(context.Background(), "bad", []byte{'a'})
require.Error(t, err)
})

t.Run("Insert", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, make([]byte, 1024))
require.NoError(t, err)
})

t.Run("InsertAlreadyExists", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
data := make([]byte, 1024)
_, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data)
Expand Down
16 changes: 8 additions & 8 deletions coderd/projectimport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestPostProjectImportByOrganization(t *testing.T) {
t.Parallel()
t.Run("FileNotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
_, err := client.CreateProjectImportJob(context.Background(), user.Organization, coderd.CreateProjectImportJobRequest{
StorageMethod: database.ProvisionerStorageMethodFile,
Expand All @@ -30,7 +30,7 @@ func TestPostProjectImportByOrganization(t *testing.T) {
})
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
_ = coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
})
Expand All @@ -40,7 +40,7 @@ func TestProjectImportJobSchemasByID(t *testing.T) {
t.Parallel()
t.Run("ListRunning", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
_, err := client.ProjectImportJobSchemas(context.Background(), user.Organization, job.ID)
Expand All @@ -50,7 +50,7 @@ func TestProjectImportJobSchemasByID(t *testing.T) {
})
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{
Expand Down Expand Up @@ -80,7 +80,7 @@ func TestProjectImportJobParametersByID(t *testing.T) {
t.Parallel()
t.Run("ListRunning", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
_, err := client.ProjectImportJobSchemas(context.Background(), user.Organization, job.ID)
Expand All @@ -90,7 +90,7 @@ func TestProjectImportJobParametersByID(t *testing.T) {
})
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{
Expand Down Expand Up @@ -126,7 +126,7 @@ func TestProjectImportJobResourcesByID(t *testing.T) {
t.Parallel()
t.Run("ListRunning", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
_, err := client.ProjectImportJobResources(context.Background(), user.Organization, job.ID)
Expand All @@ -136,7 +136,7 @@ func TestProjectImportJobResourcesByID(t *testing.T) {
})
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, &echo.Responses{
Expand Down
22 changes: 11 additions & 11 deletions coderd/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestProjects(t *testing.T) {

t.Run("ListEmpty", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
projects, err := client.Projects(context.Background(), "")
require.NoError(t, err)
Expand All @@ -28,7 +28,7 @@ func TestProjects(t *testing.T) {

t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
_ = coderdtest.CreateProject(t, client, user.Organization, job.ID)
Expand All @@ -39,7 +39,7 @@ func TestProjects(t *testing.T) {

t.Run("ListWorkspaceOwnerCount", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
Expand All @@ -58,7 +58,7 @@ func TestProjectsByOrganization(t *testing.T) {
t.Parallel()
t.Run("ListEmpty", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
projects, err := client.Projects(context.Background(), user.Organization)
require.NoError(t, err)
Expand All @@ -68,7 +68,7 @@ func TestProjectsByOrganization(t *testing.T) {

t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
_ = coderdtest.CreateProject(t, client, user.Organization, job.ID)
Expand All @@ -82,15 +82,15 @@ func TestPostProjectsByOrganization(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
_ = coderdtest.CreateProject(t, client, user.Organization, job.ID)
})

t.Run("AlreadyExists", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
Expand All @@ -108,7 +108,7 @@ func TestProjectByOrganization(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
Expand All @@ -121,7 +121,7 @@ func TestPostParametersByProject(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
Expand All @@ -139,7 +139,7 @@ func TestParametersByProject(t *testing.T) {
t.Parallel()
t.Run("ListEmpty", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
Expand All @@ -150,7 +150,7 @@ func TestParametersByProject(t *testing.T) {

t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
Expand Down

0 comments on commit 8958b64

Please sign in to comment.