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
This commit is contained in:
Kyle Carberry
2022-02-21 20:36:29 +00:00
committed by GitHub
parent 67613da86d
commit 8958b641e9
41 changed files with 752 additions and 251 deletions
+2 -2
View File
@@ -14,13 +14,13 @@ func TestUpload(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.UploadFile(context.Background(), "wow", []byte{})
require.Error(t, err)
})
t.Run("Upload", 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, []byte{'a'})
require.NoError(t, err)
+11 -11
View File
@@ -18,14 +18,14 @@ func TestCreateProjectImportJob(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateProjectImportJob(context.Background(), "", coderd.CreateProjectImportJobRequest{})
require.Error(t, err)
})
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)
})
@@ -35,14 +35,14 @@ func TestProjectImportJob(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectImportJob(context.Background(), "", uuid.New())
require.Error(t, err)
})
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)
_, err := client.ProjectImportJob(context.Background(), user.Organization, job.ID)
@@ -54,14 +54,14 @@ func TestProjectImportJobLogsBefore(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectImportJobLogsBefore(context.Background(), "", uuid.New(), time.Time{})
require.Error(t, err)
})
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
before := time.Now()
@@ -85,14 +85,14 @@ func TestProjectImportJobLogsAfter(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectImportJobLogsAfter(context.Background(), "", uuid.New(), time.Time{})
require.Error(t, err)
})
t.Run("Get", 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{
@@ -120,7 +120,7 @@ func TestProjectImportJobSchemas(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectImportJobSchemas(context.Background(), "", uuid.New())
require.Error(t, err)
})
@@ -130,7 +130,7 @@ func TestProjectImportJobParameters(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectImportJobParameters(context.Background(), "", uuid.New())
require.Error(t, err)
})
@@ -140,7 +140,7 @@ func TestProjectImportJobResources(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectImportJobResources(context.Background(), "", uuid.New())
require.Error(t, err)
})
+16 -16
View File
@@ -16,14 +16,14 @@ func TestProjects(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.Projects(context.Background(), "")
require.Error(t, err)
})
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.Projects(context.Background(), "")
require.NoError(t, err)
@@ -34,14 +34,14 @@ func TestProject(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.Project(context.Background(), "", "")
require.Error(t, err)
})
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)
@@ -54,7 +54,7 @@ func TestCreateProject(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateProject(context.Background(), "org", coderd.CreateProjectRequest{
Name: "something",
VersionImportJobID: uuid.New(),
@@ -64,7 +64,7 @@ func TestCreateProject(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)
job := coderdtest.CreateProjectImportJob(t, client, user.Organization, nil)
_ = coderdtest.CreateProject(t, client, user.Organization, job.ID)
@@ -75,14 +75,14 @@ func TestProjectVersions(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectVersions(context.Background(), "some", "project")
require.Error(t, err)
})
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)
@@ -95,14 +95,14 @@ func TestProjectVersion(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectVersion(context.Background(), "some", "project", "version")
require.Error(t, err)
})
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)
@@ -115,14 +115,14 @@ func TestCreateProjectVersion(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateProjectVersion(context.Background(), "some", "project", coderd.CreateProjectVersionRequest{})
require.Error(t, err)
})
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)
@@ -137,14 +137,14 @@ func TestProjectParameters(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProjectParameters(context.Background(), "some", "project")
require.Error(t, err)
})
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)
@@ -157,14 +157,14 @@ func TestCreateProjectParameter(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateProjectParameter(context.Background(), "some", "project", coderd.CreateParameterValueRequest{})
require.Error(t, err)
})
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)
+3 -3
View File
@@ -14,7 +14,7 @@ func TestProvisionerDaemons(t *testing.T) {
t.Parallel()
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ProvisionerDaemons(context.Background())
require.NoError(t, err)
})
@@ -24,7 +24,7 @@ func TestProvisionerDaemonClient(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
ctx, cancelFunc := context.WithCancel(context.Background())
daemon, err := client.ProvisionerDaemonClient(ctx)
require.NoError(t, err)
@@ -35,7 +35,7 @@ func TestProvisionerDaemonClient(t *testing.T) {
t.Run("Connect", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
daemon, err := client.ProvisionerDaemonClient(ctx)
+13 -13
View File
@@ -14,7 +14,7 @@ func TestHasInitialUser(t *testing.T) {
t.Parallel()
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
has, err := client.HasInitialUser(context.Background())
require.NoError(t, err)
require.False(t, has)
@@ -22,7 +22,7 @@ func TestHasInitialUser(t *testing.T) {
t.Run("Found", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
has, err := client.HasInitialUser(context.Background())
require.NoError(t, err)
@@ -34,14 +34,14 @@ func TestCreateInitialUser(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{})
require.Error(t, err)
})
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
})
}
@@ -50,14 +50,14 @@ func TestCreateUser(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateUser(context.Background(), coderd.CreateUserRequest{})
require.Error(t, err)
})
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.CreateUser(context.Background(), coderd.CreateUserRequest{
Email: "example@coder.com",
@@ -72,14 +72,14 @@ func TestLoginWithPassword(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{})
require.Error(t, err)
})
t.Run("Success", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
user := coderdtest.CreateInitialUser(t, client)
_, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
Email: user.Email,
@@ -91,7 +91,7 @@ func TestLoginWithPassword(t *testing.T) {
func TestLogout(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
err := client.Logout(context.Background())
require.NoError(t, err)
}
@@ -100,14 +100,14 @@ func TestUser(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.User(context.Background(), "")
require.Error(t, err)
})
t.Run("Get", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.User(context.Background(), "")
require.NoError(t, err)
@@ -118,14 +118,14 @@ func TestUserOrganizations(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.UserOrganizations(context.Background(), "")
require.Error(t, err)
})
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.UserOrganizations(context.Background(), "")
require.NoError(t, err)
+44
View File
@@ -0,0 +1,44 @@
package codersdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"cloud.google.com/go/compute/metadata"
"golang.org/x/xerrors"
"github.com/coder/coder/coderd"
)
// AuthenticateWorkspaceAgentUsingGoogleCloudIdentity uses the Google Compute Engine Metadata API to
// fetch a signed JWT, and exchange it for a session token for a workspace agent.
//
// The requesting instance must be registered as a resource in the latest history for a workspace.
func (c *Client) AuthenticateWorkspaceAgentUsingGoogleCloudIdentity(ctx context.Context, serviceAccount string, gcpClient *metadata.Client) (coderd.WorkspaceAgentAuthenticateResponse, error) {
if serviceAccount == "" {
// This is the default name specified by Google.
serviceAccount = "default"
}
if gcpClient == nil {
gcpClient = metadata.NewClient(c.httpClient)
}
// "format=full" is required, otherwise the responding payload will be missing "instance_id".
jwt, err := gcpClient.Get(fmt.Sprintf("instance/service-accounts/%s/identity?audience=coder&format=full", serviceAccount))
if err != nil {
return coderd.WorkspaceAgentAuthenticateResponse{}, xerrors.Errorf("get metadata identity: %w", err)
}
res, err := c.request(ctx, http.MethodPost, "/api/v2/workspaceagent/authenticate/google-instance-identity", coderd.GoogleInstanceIdentityToken{
JSONWebToken: jwt,
})
if err != nil {
return coderd.WorkspaceAgentAuthenticateResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.WorkspaceAgentAuthenticateResponse{}, readBodyAsError(res)
}
var resp coderd.WorkspaceAgentAuthenticateResponse
return resp, json.NewDecoder(res.Body).Decode(&resp)
}
+37
View File
@@ -0,0 +1,37 @@
package codersdk_test
import (
"bytes"
"context"
"io"
"net/http"
"testing"
"cloud.google.com/go/compute/metadata"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/coderdtest"
)
func TestAuthenticateWorkspaceAgentUsingGoogleCloudIdentity(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
_, err := client.AuthenticateWorkspaceAgentUsingGoogleCloudIdentity(context.Background(), "", metadata.NewClient(&http.Client{
Transport: roundTripper(func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader([]byte("sometoken"))),
}, nil
}),
}))
require.Error(t, err)
})
}
type roundTripper func(req *http.Request) (*http.Response, error)
func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return r(req)
}
+14 -14
View File
@@ -15,14 +15,14 @@ func TestWorkspaces(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.Workspaces(context.Background(), "")
require.Error(t, err)
})
t.Run("List", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.Workspaces(context.Background(), "")
require.NoError(t, err)
@@ -33,14 +33,14 @@ func TestWorkspacesByProject(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.WorkspacesByProject(context.Background(), "", "")
require.Error(t, err)
})
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)
@@ -53,14 +53,14 @@ func TestWorkspace(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.Workspace(context.Background(), "", "")
require.Error(t, err)
})
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)
@@ -74,14 +74,14 @@ func TestListWorkspaceHistory(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.ListWorkspaceHistory(context.Background(), "", "")
require.Error(t, err)
})
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)
@@ -95,14 +95,14 @@ func TestWorkspaceHistory(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.WorkspaceHistory(context.Background(), "", "", "")
require.Error(t, err)
})
t.Run("Get", 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)
@@ -121,14 +121,14 @@ func TestCreateWorkspace(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{})
require.Error(t, err)
})
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)
@@ -140,14 +140,14 @@ func TestCreateWorkspaceHistory(t *testing.T) {
t.Parallel()
t.Run("Error", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
client := coderdtest.New(t, nil)
_, err := client.CreateWorkspaceHistory(context.Background(), "", "", coderd.CreateWorkspaceHistoryRequest{})
require.Error(t, err)
})
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, nil)