feat: pass access_token to coder_git_auth resource (#6713)

This allows template authors to leverage git auth to perform
custom actions, like clone repositories.
This commit is contained in:
Kyle Carberry
2023-03-22 19:37:08 +00:00
committed by GitHub
parent 79ae7cd639
commit df31636e72
20 changed files with 647 additions and 479 deletions
@@ -28,11 +28,11 @@ import (
"github.com/coder/coder/coderd/audit"
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/dbauthz"
"github.com/coder/coder/coderd/gitauth"
"github.com/coder/coder/coderd/httpmw"
"github.com/coder/coder/coderd/parameter"
"github.com/coder/coder/coderd/schedule"
"github.com/coder/coder/coderd/telemetry"
"github.com/coder/coder/coderd/util/slice"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner"
"github.com/coder/coder/provisionerd/proto"
@@ -50,7 +50,7 @@ type Server struct {
ID uuid.UUID
Logger slog.Logger
Provisioners []database.ProvisionerType
GitAuthProviders []string
GitAuthConfigs []*gitauth.Config
Tags json.RawMessage
Database database.Store
Pubsub database.Pubsub
@@ -210,6 +210,48 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
return nil, failJob(fmt.Sprintf("get workspace build parameters: %s", err))
}
gitAuthProviders := []*sdkproto.GitAuthProvider{}
for _, p := range templateVersion.GitAuthProviders {
link, err := server.Database.GetGitAuthLink(ctx, database.GetGitAuthLinkParams{
ProviderID: p,
UserID: owner.ID,
})
if errors.Is(err, sql.ErrNoRows) {
continue
}
if err != nil {
return nil, failJob(fmt.Sprintf("acquire git auth link: %s", err))
}
var config *gitauth.Config
for _, c := range server.GitAuthConfigs {
if c.ID != p {
continue
}
config = c
break
}
// We weren't able to find a matching config for the ID!
if config == nil {
server.Logger.Warn(ctx, "workspace build job is missing git provider",
slog.F("git_provider_id", p),
slog.F("template_version_id", templateVersion.ID),
slog.F("workspace_id", workspaceBuild.WorkspaceID))
continue
}
link, valid, err := config.RefreshToken(ctx, server.Database, link)
if err != nil {
return nil, failJob(fmt.Sprintf("refresh git auth link %q: %s", p, err))
}
if !valid {
continue
}
gitAuthProviders = append(gitAuthProviders, &sdkproto.GitAuthProvider{
Id: p,
AccessToken: link.OAuthAccessToken,
})
}
protoJob.Type = &proto.AcquiredJob_WorkspaceBuild_{
WorkspaceBuild: &proto.AcquiredJob_WorkspaceBuild{
WorkspaceBuildId: workspaceBuild.ID.String(),
@@ -218,6 +260,7 @@ func (server *Server) AcquireJob(ctx context.Context, _ *proto.Empty) (*proto.Ac
ParameterValues: protoParameters,
RichParameterValues: convertRichParameterValues(workspaceBuildParameters),
VariableValues: asVariableValues(templateVariables),
GitAuthProviders: gitAuthProviders,
Metadata: &sdkproto.Provision_Metadata{
CoderUrl: server.AccessURL.String(),
WorkspaceTransition: transition,
@@ -857,7 +900,14 @@ func (server *Server) CompleteJob(ctx context.Context, completed *proto.Complete
var completedError sql.NullString
for _, gitAuthProvider := range jobType.TemplateImport.GitAuthProviders {
if !slice.Contains(server.GitAuthProviders, gitAuthProvider) {
contains := false
for _, configuredProvider := range server.GitAuthConfigs {
if configuredProvider.ID == gitAuthProvider {
contains = true
break
}
}
if !contains {
completedError = sql.NullString{
String: fmt.Sprintf("git auth provider %q is not configured", gitAuthProvider),
Valid: true,
@@ -12,6 +12,7 @@ import (
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/dbfake"
"github.com/coder/coder/coderd/database/dbgen"
"github.com/coder/coder/testutil"
)
func TestObtainOIDCAccessToken(t *testing.T) {
@@ -45,11 +46,9 @@ func TestObtainOIDCAccessToken(t *testing.T) {
LoginType: database.LoginTypeOIDC,
OAuthExpiry: database.Now().Add(-time.Hour),
})
_, err := obtainOIDCAccessToken(ctx, db, &oauth2Config{
tokenSource: func() (*oauth2.Token, error) {
return &oauth2.Token{
AccessToken: "token",
}, nil
_, err := obtainOIDCAccessToken(ctx, db, &testutil.OAuth2Config{
Token: &oauth2.Token{
AccessToken: "token",
},
}, user.ID)
require.NoError(t, err)
@@ -61,25 +60,3 @@ func TestObtainOIDCAccessToken(t *testing.T) {
require.Equal(t, "token", link.OAuthAccessToken)
})
}
type oauth2Config struct {
tokenSource oauth2TokenSource
}
func (o *oauth2Config) TokenSource(context.Context, *oauth2.Token) oauth2.TokenSource {
return o.tokenSource
}
func (*oauth2Config) AuthCodeURL(string, ...oauth2.AuthCodeOption) string {
return ""
}
func (*oauth2Config) Exchange(context.Context, string, ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
return &oauth2.Token{}, nil
}
type oauth2TokenSource func() (*oauth2.Token, error)
func (o oauth2TokenSource) Token() (*oauth2.Token, error) {
return o()
}
@@ -18,6 +18,7 @@ import (
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/database/dbfake"
"github.com/coder/coder/coderd/database/dbgen"
"github.com/coder/coder/coderd/gitauth"
"github.com/coder/coder/coderd/provisionerdserver"
"github.com/coder/coder/coderd/schedule"
"github.com/coder/coder/coderd/telemetry"
@@ -98,6 +99,11 @@ func TestAcquireJob(t *testing.T) {
t.Run("WorkspaceBuildJob", func(t *testing.T) {
t.Parallel()
srv := setup(t, false)
gitAuthProvider := "github"
srv.GitAuthConfigs = []*gitauth.Config{{
ID: gitAuthProvider,
OAuth2Config: &testutil.OAuth2Config{},
}}
ctx := context.Background()
user := dbgen.User(t, srv.Database, database.User{})
@@ -107,6 +113,10 @@ func TestAcquireJob(t *testing.T) {
OAuthExpiry: database.Now().Add(time.Hour),
OAuthAccessToken: "access-token",
})
dbgen.GitAuthLink(t, srv.Database, database.GitAuthLink{
ProviderID: gitAuthProvider,
UserID: user.ID,
})
template := dbgen.Template(t, srv.Database, database.Template{
Name: "template",
Provisioner: database.ProvisionerTypeEcho,
@@ -120,6 +130,12 @@ func TestAcquireJob(t *testing.T) {
},
JobID: uuid.New(),
})
err := srv.Database.UpdateTemplateVersionGitAuthProvidersByJobID(ctx, database.UpdateTemplateVersionGitAuthProvidersByJobIDParams{
JobID: version.JobID,
GitAuthProviders: []string{gitAuthProvider},
UpdatedAt: database.Now(),
})
require.NoError(t, err)
// Import version job
_ = dbgen.ProvisionerJob(t, srv.Database, database.ProvisionerJob{
ID: version.JobID,
@@ -214,6 +230,10 @@ func TestAcquireJob(t *testing.T) {
Value: "second_value",
},
},
GitAuthProviders: []*sdkproto.GitAuthProvider{{
Id: gitAuthProvider,
AccessToken: "access_token",
}},
Metadata: &sdkproto.Provision_Metadata{
CoderUrl: srv.AccessURL.String(),
WorkspaceTransition: sdkproto.WorkspaceTransition_START,
@@ -795,7 +815,9 @@ func TestCompleteJob(t *testing.T) {
job, err = srv.Database.GetProvisionerJobByID(ctx, job.ID)
require.NoError(t, err)
require.Contains(t, job.Error.String, `git auth provider "github" is not configured`)
srv.GitAuthProviders = []string{"github"}
srv.GitAuthConfigs = []*gitauth.Config{{
ID: "github",
}}
completeJob()
job, err = srv.Database.GetProvisionerJobByID(ctx, job.ID)
require.NoError(t, err)
@@ -930,8 +952,7 @@ func TestCompleteJob(t *testing.T) {
Valid: true,
}
}
workspace, err := srv.Database.InsertWorkspace(ctx, database.InsertWorkspaceParams{
ID: uuid.New(),
workspace := dbgen.Workspace(t, srv.Database, database.Workspace{
TemplateID: template.ID,
Ttl: workspaceTTL,
})
@@ -942,26 +963,19 @@ func TestCompleteJob(t *testing.T) {
},
JobID: uuid.New(),
})
require.NoError(t, err)
build, err := srv.Database.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
ID: uuid.New(),
build := dbgen.WorkspaceBuild(t, srv.Database, database.WorkspaceBuild{
WorkspaceID: workspace.ID,
TemplateVersionID: version.ID,
Transition: c.transition,
Reason: database.BuildReasonInitiator,
})
require.NoError(t, err)
job, err := srv.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
ID: uuid.New(),
FileID: file.ID,
Provisioner: database.ProvisionerTypeEcho,
Type: database.ProvisionerJobTypeWorkspaceBuild,
StorageMethod: database.ProvisionerStorageMethodFile,
job := dbgen.ProvisionerJob(t, srv.Database, database.ProvisionerJob{
FileID: file.ID,
Type: database.ProvisionerJobTypeWorkspaceBuild,
Input: must(json.Marshal(provisionerdserver.WorkspaceProvisionJob{
WorkspaceBuildID: build.ID,
})),
})
require.NoError(t, err)
_, err = srv.Database.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
WorkerID: uuid.NullUUID{
UUID: srv.ID,
@@ -1022,7 +1036,6 @@ func TestCompleteJob(t *testing.T) {
})
}
})
t.Run("TemplateDryRun", func(t *testing.T) {
t.Parallel()
srv := setup(t, false)