From 5924afb014951443900414868f232920086ff28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Samin?= Date: Fri, 17 Nov 2023 11:51:11 +0100 Subject: [PATCH 1/3] feat(api): valid email addresses for GPG keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: François Samin --- engine/api/api.go | 13 +++--- engine/api/application.go | 14 ++++++- engine/api/application/application_parser.go | 12 +++++- .../application/application_parser_test.go | 8 ++-- engine/api/application_export_test.go | 2 +- engine/api/application_import.go | 2 +- engine/api/application_import_test.go | 6 +-- engine/api/application_key.go | 13 +++++- engine/api/application_key_test.go | 2 +- engine/api/ascode.go | 2 +- engine/api/ascode_test.go | 2 +- engine/api/environment.go | 14 ++++++- engine/api/environment/environment_parser.go | 12 +++++- .../environment/environment_parser_test.go | 8 ++-- engine/api/environment_export_test.go | 2 +- engine/api/environment_import.go | 2 +- engine/api/environment_import_test.go | 4 +- engine/api/environment_key.go | 15 ++++++- engine/api/environment_key_test.go | 2 +- engine/api/keys/key.go | 14 ------- engine/api/keys/keys_pgp.go | 12 +++--- engine/api/keys/keys_test.go | 2 +- engine/api/keys/parse.go | 25 +++++++++--- engine/api/project.go | 14 ++++++- engine/api/project/dao.go | 2 +- engine/api/project_key.go | 40 ++++++++++++++++++- engine/api/project_key_test.go | 2 +- engine/api/repositories_manager_test.go | 2 +- engine/api/templates.go | 2 +- engine/api/user_gpgkey_test.go | 2 +- engine/api/v2_project_vcs_test.go | 11 ++--- engine/api/workflow/dao.go | 6 +-- engine/api/workflow/dao_node_run_test.go | 4 +- engine/api/workflow/dao_run_test.go | 6 +-- engine/api/workflow/dao_test.go | 10 ++--- engine/api/workflow/process_node_test.go | 8 ++-- engine/api/workflow/repository.go | 8 ++-- engine/api/workflow_ascode_test.go | 4 +- engine/api/workflow_import.go | 2 +- engine/api/workflow_import_test.go | 2 +- engine/api/workflow_queue_test.go | 15 +++++-- engine/api/workflow_run.go | 4 +- engine/api/workflow_template_bulk.go | 2 +- engine/api/workflow_test.go | 4 +- 44 files changed, 231 insertions(+), 107 deletions(-) delete mode 100644 engine/api/keys/key.go diff --git a/engine/api/api.go b/engine/api/api.go index 2c245aa51d..34efee53f3 100644 --- a/engine/api/api.go +++ b/engine/api/api.go @@ -241,9 +241,10 @@ type Configuration struct { CustomServiceJobBookDelay map[string]int64 `toml:"customServiceJobBookDelay" comment:"Set custom job book delay for given CDS Hatchery (in seconds)" json:"customServiceJobBookDelay" commented:"true"` } `toml:"workflow" comment:"######################\n 'Workflow' global configuration \n######################" json:"workflow"` Project struct { - CreationDisabled bool `toml:"creationDisabled" comment:"Disable project creation for CDS non admin users." json:"creationDisabled" default:"false" commented:"true"` - InfoCreationDisabled string `toml:"infoCreationDisabled" comment:"Optional message to display if project creation is disabled." json:"infoCreationDisabled" default:"" commented:"true"` - VCSManagementDisabled bool `toml:"vcsManagementDisabled" comment:"Disable VCS management on project for CDS non admin users." json:"vcsManagementDisabled" default:"false" commented:"true"` + CreationDisabled bool `toml:"creationDisabled" comment:"Disable project creation for CDS non admin users." json:"creationDisabled" default:"false" commented:"true"` + InfoCreationDisabled string `toml:"infoCreationDisabled" comment:"Optional message to display if project creation is disabled." json:"infoCreationDisabled" default:"" commented:"true"` + VCSManagementDisabled bool `toml:"vcsManagementDisabled" comment:"Disable VCS management on project for CDS non admin users." json:"vcsManagementDisabled" default:"false" commented:"true"` + GPGKeyEmailAddressTemplate string `toml:"gpgKeyEmailAddressTemplate" comment:"Template for GPG Keys email address" json:"gpgKeyEmailAddressTemplate" default:"noreply+cds-{{.ProjectKey}}-{{.KeyName}}@localhost.local" commented:"true"` } `toml:"project" comment:"######################\n 'Project' global configuration \n######################" json:"project"` EventBus event.Config `toml:"events" comment:"######################\n Event bus configuration \n######################" json:"events" mapstructure:"events"` VCS struct { @@ -376,7 +377,7 @@ func (a *API) CheckConfiguration(config interface{}) error { if ok, err := sdk.DirectoryExists(aConfig.Download.Directory); !ok { if err := os.MkdirAll(aConfig.Download.Directory, os.FileMode(0700)); err != nil { - return fmt.Errorf("Unable to create directory %s: %v", aConfig.Download.Directory, err) + return fmt.Errorf("unable to create directory %s: %v", aConfig.Download.Directory, err) } log.Info(context.Background(), "Directory %s has been created", aConfig.Download.Directory) } else if err != nil { @@ -395,7 +396,7 @@ func (a *API) CheckConfiguration(config interface{}) error { } if ok, err := sdk.DirectoryExists(aConfig.Artifact.Local.BaseDirectory); !ok { if err := os.MkdirAll(aConfig.Artifact.Local.BaseDirectory, os.FileMode(0700)); err != nil { - return fmt.Errorf("Unable to create directory %s: %v", aConfig.Artifact.Local.BaseDirectory, err) + return fmt.Errorf("unable to create directory %s: %v", aConfig.Artifact.Local.BaseDirectory, err) } log.Info(context.Background(), "Directory %s has been created", aConfig.Artifact.Local.BaseDirectory) } else if err != nil { @@ -411,7 +412,7 @@ func (a *API) CheckConfiguration(config interface{}) error { } if (aConfig.DefaultOS == "" && aConfig.DefaultArch != "") || (aConfig.DefaultOS != "" && aConfig.DefaultArch == "") { - return fmt.Errorf("You can't specify just defaultArch without defaultOS in your configuration and vice versa") + return fmt.Errorf("you can't specify just defaultArch without defaultOS in your configuration and vice versa") } if aConfig.Auth.RSAPrivateKey == "" && len(aConfig.Auth.RSAPrivateKeys) == 0 { diff --git a/engine/api/application.go b/engine/api/application.go index 7bfed1bfb1..7e4c81832a 100644 --- a/engine/api/application.go +++ b/engine/api/application.go @@ -500,7 +500,19 @@ func (api *API) updateAsCodeApplicationHandler() service.Handler { // create keys for i := range a.Keys { k := &a.Keys[i] - newKey, err := keys.GenerateKey(k.Name, k.Type) + var newKey sdk.Key + var err error + switch k.Type { + case sdk.KeyTypePGP: + var email string + email, err = api.gpgKeyEmailAddress(ctx, key, k.Name) + if err != nil { + return err + } + newKey, err = keys.GeneratePGPKeyPair(k.Name, "Application key generated by CDS", email) + case sdk.KeyTypeSSH: + newKey, err = keys.GenerateSSHKey(k.Name) + } if err != nil { return err } diff --git a/engine/api/application/application_parser.go b/engine/api/application/application_parser.go index 9353ba077a..f92703a6cb 100644 --- a/engine/api/application/application_parser.go +++ b/engine/api/application/application_parser.go @@ -23,7 +23,7 @@ type ImportOptions struct { } // ParseAndImport parse an exportentities.Application and insert or update the application in database -func ParseAndImport(ctx context.Context, db gorpmapper.SqlExecutorWithTx, cache cache.Store, proj sdk.Project, eapp *exportentities.Application, opts ImportOptions, decryptFunc keys.DecryptFunc, u sdk.Identifiable) (*sdk.Application, []sdk.Variable, []sdk.Message, error) { +func ParseAndImport(ctx context.Context, db gorpmapper.SqlExecutorWithTx, cache cache.Store, proj sdk.Project, eapp *exportentities.Application, opts ImportOptions, decryptFunc keys.DecryptFunc, u sdk.Identifiable, emailFunc keys.EmailFunc) (*sdk.Application, []sdk.Variable, []sdk.Message, error) { log.Info(ctx, "ParseAndImport>> Import application %s in project %s (force=%v)", eapp.Name, proj.Key, opts.Force) msgList := []sdk.Message{} @@ -124,7 +124,15 @@ func ParseAndImport(ctx context.Context, db gorpmapper.SqlExecutorWithTx, cache keepOldValue = true } - kk, err := keys.Parse(ctx, db, proj.ID, kname, kval, decryptFunc) + var gpgEmail string + if kval.Type == sdk.KeyPGPParameter { + gpgEmail, err = emailFunc(ctx, proj.Key, kname) + if err != nil { + return app, nil, msgList, sdk.ErrorWithFallback(err, sdk.ErrWrongRequest, "unable to parse key %s", kname) + } + } + + kk, err := keys.Parse(ctx, db, proj.ID, kname, kval, decryptFunc, gpgEmail) if err != nil { return app, nil, msgList, sdk.ErrorWithFallback(err, sdk.ErrWrongRequest, "unable to parse key %s", kname) } diff --git a/engine/api/application/application_parser_test.go b/engine/api/application/application_parser_test.go index d1d76b3aba..02571193de 100644 --- a/engine/api/application/application_parser_test.go +++ b/engine/api/application/application_parser_test.go @@ -40,15 +40,15 @@ name: ` + appName + ` require.NoError(t, errapp) // try to import without force, it must give an error - _, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: false}, nil, u) + _, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: false}, nil, u, nil) require.Error(t, globalError) // try to import with force, but with another repository, it must give an error - _, _, _, globalError2 := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true, FromRepository: "bar"}, nil, u) + _, _, _, globalError2 := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true, FromRepository: "bar"}, nil, u, nil) require.Error(t, globalError2) // try to import with force, without a repo, it's ok - _, _, _, globalError3 := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + _, _, _, globalError3 := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) require.NoError(t, globalError3) } @@ -96,7 +96,7 @@ name: ` + appName + ` assert.Equal(t, 1, len(events)) // try to import with force, without a repo, it's ok - _, _, _, globalError3 := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + _, _, _, globalError3 := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) require.NoError(t, globalError3) events, err = ascode.LoadEventsByWorkflowID(context.TODO(), db, wf.ID) diff --git a/engine/api/application_export_test.go b/engine/api/application_export_test.go index f46a81a9e5..d7bdf996b9 100644 --- a/engine/api/application_export_test.go +++ b/engine/api/application_export_test.go @@ -46,7 +46,7 @@ func Test_getApplicationExportHandler(t *testing.T) { Type: sdk.KeyTypePGP, ApplicationID: app.ID, } - kk, err := keys.GeneratePGPKeyPair(k.Name) + kk, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") require.NoError(t, err) k.Public = kk.Public diff --git a/engine/api/application_import.go b/engine/api/application_import.go index 4e4c00859f..f4c688a745 100644 --- a/engine/api/application_import.go +++ b/engine/api/application_import.go @@ -61,7 +61,7 @@ func (api *API) postApplicationImportHandler() service.Handler { } defer tx.Rollback() // nolint - newApp, _, msgList, globalError := application.ParseAndImport(ctx, tx, api.Cache, *proj, eapp, application.ImportOptions{Force: force}, project.DecryptWithBuiltinKey, getUserConsumer(ctx)) + newApp, _, msgList, globalError := application.ParseAndImport(ctx, tx, api.Cache, *proj, eapp, application.ImportOptions{Force: force}, project.DecryptWithBuiltinKey, getUserConsumer(ctx), api.gpgKeyEmailAddress) msgListString := translate(msgList) if globalError != nil { globalError = sdk.WrapError(globalError, "Unable to import application %s", eapp.Name) diff --git a/engine/api/application_import_test.go b/engine/api/application_import_test.go index 6208a3a1b3..7013f267eb 100644 --- a/engine/api/application_import_test.go +++ b/engine/api/application_import_test.go @@ -111,7 +111,7 @@ func Test_postApplicationImportHandler_NewAppFromYAMLWithKeysAndSecrets(t *testi ApplicationID: app.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k.Name) + kpgp, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") require.NoError(t, err) k.Public = kpgp.Public k.Private = kpgp.Private @@ -219,7 +219,7 @@ func Test_postApplicationImportHandler_NewAppFromYAMLWithKeysAndSecretsAndReImpo ApplicationID: app.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k.Name) + kpgp, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") require.NoError(t, err) k.Public = kpgp.Public k.Private = kpgp.Private @@ -383,7 +383,7 @@ func Test_postApplicationImportHandler_NewAppFromYAMLWithKeysAndSecretsAndReImpo ApplicationID: app.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k1.Name) + kpgp, err := keys.GeneratePGPKeyPair(k1.Name, "", "test@cds") require.NoError(t, err) k1.Public = kpgp.Public k1.Private = kpgp.Private diff --git a/engine/api/application_key.go b/engine/api/application_key.go index 374291e7fe..6f7bb738b4 100644 --- a/engine/api/application_key.go +++ b/engine/api/application_key.go @@ -104,7 +104,18 @@ func (api *API) addKeyInApplicationHandler() service.Handler { newKey.Name = "app-" + newKey.Name } - k, err := keys.GenerateKey(newKey.Name, newKey.Type) + var k sdk.Key + switch newKey.Type { + case sdk.KeyTypePGP: + var email string + email, err = api.gpgKeyEmailAddress(ctx, key, newKey.Name) + if err != nil { + return err + } + k, err = keys.GeneratePGPKeyPair(newKey.Name, "Application Key generated by CDS", email) + case sdk.KeyTypeSSH: + k, err = keys.GenerateSSHKey(newKey.Name) + } if err != nil { return err } diff --git a/engine/api/application_key_test.go b/engine/api/application_key_test.go index 48bebd932c..723ab930b5 100644 --- a/engine/api/application_key_test.go +++ b/engine/api/application_key_test.go @@ -39,7 +39,7 @@ func Test_getKeysInApplicationHandler(t *testing.T) { ApplicationID: app.ID, } - pgpK, err := keys.GeneratePGPKeyPair(k.Name) + pgpK, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") if err != nil { t.Fatal(err) } diff --git a/engine/api/ascode.go b/engine/api/ascode.go index 65089573b5..4eaa937c1f 100644 --- a/engine/api/ascode.go +++ b/engine/api/ascode.go @@ -193,7 +193,7 @@ func (api *API) postPerformImportAsCodeHandler() service.Handler { if err != nil { return err } - msgPush, wrkflw, _, _, err := workflow.Push(ctx, api.mustDB(), api.Cache, proj, data, opt, getUserConsumer(ctx), project.DecryptWithBuiltinKey) + msgPush, wrkflw, _, _, err := workflow.Push(ctx, api.mustDB(), api.Cache, proj, data, opt, getUserConsumer(ctx), project.DecryptWithBuiltinKey, api.gpgKeyEmailAddress) allMsg = append(allMsg, msgPush...) if err != nil { return sdk.WrapError(err, "unable to push workflow") diff --git a/engine/api/ascode_test.go b/engine/api/ascode_test.go index e3064c4a64..9e5698aca7 100644 --- a/engine/api/ascode_test.go +++ b/engine/api/ascode_test.go @@ -303,7 +303,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.Background(), db, api.Cache, *p, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.Background(), db, api.Cache, *p, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) app.FromRepository = repoURL diff --git a/engine/api/environment.go b/engine/api/environment.go index 9d63ef7261..7e3baf688f 100644 --- a/engine/api/environment.go +++ b/engine/api/environment.go @@ -325,7 +325,19 @@ func (api *API) updateAsCodeEnvironmentHandler() service.Handler { for i := range env.Keys { k := &env.Keys[i] if k.ID == 0 { - newKey, err := keys.GenerateKey(k.Name, k.Type) + var newKey sdk.Key + var err error + switch k.Type { + case sdk.KeyTypePGP: + var email string + email, err = api.gpgKeyEmailAddress(ctx, key, k.Name) + if err != nil { + return err + } + newKey, err = keys.GeneratePGPKeyPair(k.Name, "Environment Key generated by CDS", email) // TODO email address + case sdk.KeyTypeSSH: + newKey, err = keys.GenerateSSHKey(k.Name) + } if err != nil { return err } diff --git a/engine/api/environment/environment_parser.go b/engine/api/environment/environment_parser.go index 668ee5a165..56f9aaf6ec 100644 --- a/engine/api/environment/environment_parser.go +++ b/engine/api/environment/environment_parser.go @@ -22,7 +22,7 @@ type ImportOptions struct { } // ParseAndImport parse an exportentities.Environment and insert or update the environment in database -func ParseAndImport(ctx context.Context, db gorpmapper.SqlExecutorWithTx, proj sdk.Project, eenv exportentities.Environment, opts ImportOptions, decryptFunc keys.DecryptFunc, u sdk.Identifiable) (*sdk.Environment, []sdk.Variable, []sdk.Message, error) { +func ParseAndImport(ctx context.Context, db gorpmapper.SqlExecutorWithTx, proj sdk.Project, eenv exportentities.Environment, opts ImportOptions, decryptFunc keys.DecryptFunc, u sdk.Identifiable, emailFunc keys.EmailFunc) (*sdk.Environment, []sdk.Variable, []sdk.Message, error) { log.Debug(ctx, "ParseAndImport>> Import environment %s in project %s from repository %q (force=%v)", eenv.Name, proj.Key, opts.FromRepository, opts.Force) log.Debug(ctx, "ParseAndImport>> Env: %+v", eenv) @@ -124,7 +124,15 @@ func ParseAndImport(ctx context.Context, db gorpmapper.SqlExecutorWithTx, proj s keepOldValue = true } - kk, err := keys.Parse(ctx, db, proj.ID, kname, kval, decryptFunc) + var gpgEmail string + if kval.Type == sdk.KeyPGPParameter { + gpgEmail, err = emailFunc(ctx, proj.Key, kname) + if err != nil { + return env, nil, nil, sdk.ErrorWithFallback(err, sdk.ErrWrongRequest, "unable to parse key %s", kname) + } + } + + kk, err := keys.Parse(ctx, db, proj.ID, kname, kval, decryptFunc, gpgEmail) if err != nil { return env, nil, nil, sdk.WrapError(err, "Unable to parse key") } diff --git a/engine/api/environment/environment_parser_test.go b/engine/api/environment/environment_parser_test.go index 3e7be62c5b..fd158af6ef 100644 --- a/engine/api/environment/environment_parser_test.go +++ b/engine/api/environment/environment_parser_test.go @@ -41,13 +41,13 @@ name: ` + envName + ` errenv := yaml.Unmarshal(body, eenv) require.NoError(t, errenv) - _, _, _, globalError := environment.ParseAndImport(context.TODO(), db, *proj, *eenv, environment.ImportOptions{Force: false}, nil, u) + _, _, _, globalError := environment.ParseAndImport(context.TODO(), db, *proj, *eenv, environment.ImportOptions{Force: false}, nil, u, nil) require.Error(t, globalError) - _, _, _, globalError2 := environment.ParseAndImport(context.TODO(), db, *proj, *eenv, environment.ImportOptions{Force: true, FromRepository: "bar"}, nil, u) + _, _, _, globalError2 := environment.ParseAndImport(context.TODO(), db, *proj, *eenv, environment.ImportOptions{Force: true, FromRepository: "bar"}, nil, u, nil) require.Error(t, globalError2) - _, _, _, globalError3 := environment.ParseAndImport(context.TODO(), db, *proj, *eenv, environment.ImportOptions{Force: true}, nil, u) + _, _, _, globalError3 := environment.ParseAndImport(context.TODO(), db, *proj, *eenv, environment.ImportOptions{Force: true}, nil, u, nil) require.NoError(t, globalError3) } func TestParseAndImportCleanAsCode(t *testing.T) { @@ -95,7 +95,7 @@ name: ` + envName + ` assert.Equal(t, 1, len(events)) // try to import with force, without a repo, it's ok - _, _, _, globalError3 := environment.ParseAndImport(context.TODO(), db, *proj, *eenv, environment.ImportOptions{Force: true}, nil, u) + _, _, _, globalError3 := environment.ParseAndImport(context.TODO(), db, *proj, *eenv, environment.ImportOptions{Force: true}, nil, u, nil) require.NoError(t, globalError3) events, err = ascode.LoadEventsByWorkflowID(context.TODO(), db, wf.ID) diff --git a/engine/api/environment_export_test.go b/engine/api/environment_export_test.go index 0379d18cf6..427cb3b06d 100644 --- a/engine/api/environment_export_test.go +++ b/engine/api/environment_export_test.go @@ -46,7 +46,7 @@ func Test_getEnvironmentExportHandler(t *testing.T) { Type: sdk.KeyTypePGP, EnvironmentID: env.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k.Name) + kpgp, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") test.NoError(t, err) k.Public = kpgp.Public diff --git a/engine/api/environment_import.go b/engine/api/environment_import.go index 63632d1087..3b891e89dd 100644 --- a/engine/api/environment_import.go +++ b/engine/api/environment_import.go @@ -58,7 +58,7 @@ func (api *API) postEnvironmentImportHandler() service.Handler { } defer tx.Rollback() // nolint - _, _, msgList, globalError := environment.ParseAndImport(ctx, tx, *proj, data, environment.ImportOptions{Force: force}, project.DecryptWithBuiltinKey, getUserConsumer(ctx)) + _, _, msgList, globalError := environment.ParseAndImport(ctx, tx, *proj, data, environment.ImportOptions{Force: force}, project.DecryptWithBuiltinKey, getUserConsumer(ctx), api.gpgKeyEmailAddress) msgListString := translate(msgList) if globalError != nil { globalError = sdk.WrapError(globalError, "Unable to import environment %s", data.Name) diff --git a/engine/api/environment_import_test.go b/engine/api/environment_import_test.go index 79f4594372..eb97930b0e 100644 --- a/engine/api/environment_import_test.go +++ b/engine/api/environment_import_test.go @@ -105,7 +105,7 @@ func Test_postEnvironmentImportHandler_NewEnvFromYAMLWithKeysAndSecrets(t *testi EnvironmentID: env.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k.Name) + kpgp, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") require.NoError(t, err) k.Public = kpgp.Public k.Private = kpgp.Private @@ -222,7 +222,7 @@ func Test_postEnvironmentImportHandler_NewEnvFromYAMLWithKeysAndSecretsAndReImpo EnvironmentID: env.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k.Name) + kpgp, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") require.NoError(t, err) k.Public = kpgp.Public k.Private = kpgp.Private diff --git a/engine/api/environment_key.go b/engine/api/environment_key.go index bcdd689a22..130c2d4039 100644 --- a/engine/api/environment_key.go +++ b/engine/api/environment_key.go @@ -102,10 +102,23 @@ func (api *API) addKeyInEnvironmentHandler() service.Handler { newKey.Name = "env-" + newKey.Name } - k, err := keys.GenerateKey(newKey.Name, newKey.Type) + var k sdk.Key + var err error + switch newKey.Type { + case sdk.KeyTypePGP: + var email string + email, err = api.gpgKeyEmailAddress(ctx, key, newKey.Name) + if err != nil { + return err + } + k, err = keys.GeneratePGPKeyPair(newKey.Name, "Environment Key generated by CDS", email) // TODO email address + case sdk.KeyTypeSSH: + k, err = keys.GenerateSSHKey(newKey.Name) + } if err != nil { return err } + newKey.Public = k.Public newKey.Private = k.Private newKey.ID = k.ID diff --git a/engine/api/environment_key_test.go b/engine/api/environment_key_test.go index ede37fdae6..0e3726f54c 100644 --- a/engine/api/environment_key_test.go +++ b/engine/api/environment_key_test.go @@ -40,7 +40,7 @@ func Test_getKeysInEnvironmentHandler(t *testing.T) { EnvironmentID: env.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k.Name) + kpgp, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") if err != nil { t.Fatal(err) } diff --git a/engine/api/keys/key.go b/engine/api/keys/key.go deleted file mode 100644 index 9de2488150..0000000000 --- a/engine/api/keys/key.go +++ /dev/null @@ -1,14 +0,0 @@ -package keys - -import "github.com/ovh/cds/sdk" - -func GenerateKey(name string, t sdk.KeyType) (sdk.Key, error) { - switch t { - case sdk.KeyTypeSSH: - return GenerateSSHKey(name) - case sdk.KeyTypePGP: - return GeneratePGPKeyPair(name) - default: - return sdk.Key{}, sdk.WrapError(sdk.ErrUnknownKeyType, "unknown key of type: %s", t) - } -} diff --git a/engine/api/keys/keys_pgp.go b/engine/api/keys/keys_pgp.go index 363c9def08..8397963042 100644 --- a/engine/api/keys/keys_pgp.go +++ b/engine/api/keys/keys_pgp.go @@ -12,7 +12,7 @@ import ( "golang.org/x/crypto/openpgp/packet" ) -//GetOpenPGPEntity returns a single entity from an armored entity list +// GetOpenPGPEntity returns a single entity from an armored entity list func GetOpenPGPEntity(r io.Reader) (*openpgp.Entity, error) { entityList, err := openpgp.ReadArmoredKeyRing(r) if err != nil { @@ -31,9 +31,9 @@ func GetOpenPGPEntity(r io.Reader) (*openpgp.Entity, error) { return entityList[0], nil } -//NewOpenPGPEntity create an openpgp Entity -func NewOpenPGPEntity(keyname string) (*openpgp.Entity, error) { - key, errE := openpgp.NewEntity(keyname, keyname, keyname+"@cds", nil) +// NewOpenPGPEntity create an openpgp Entity +func NewOpenPGPEntity(keyname, comment, email string) (*openpgp.Entity, error) { + key, errE := openpgp.NewEntity(keyname, comment, email, nil) if errE != nil { return nil, sdk.WrapError(errE, "NewOpenPGPEntity> Cannot create new entity") } @@ -88,12 +88,12 @@ func generatePGPPublicKey(key *openpgp.Entity) (io.Reader, error) { } // GeneratePGPKeyPair generates a private / public PGP key -func GeneratePGPKeyPair(name string) (sdk.Key, error) { +func GeneratePGPKeyPair(name, comment, email string) (sdk.Key, error) { k := sdk.Key{ Name: name, Type: sdk.KeyTypePGP, } - key, err := NewOpenPGPEntity(name) + key, err := NewOpenPGPEntity(name, comment, email) if err != nil { return k, err } diff --git a/engine/api/keys/keys_test.go b/engine/api/keys/keys_test.go index 929edb0d8c..4dcd9f2889 100644 --- a/engine/api/keys/keys_test.go +++ b/engine/api/keys/keys_test.go @@ -35,7 +35,7 @@ func TestGenerateSSHKeyPair(t *testing.T) { } func TestGenerateGPGKeyPair(t *testing.T) { - k, err := GeneratePGPKeyPair("mykey") + k, err := GeneratePGPKeyPair("mykey", "this is a test key", "email@localhost.local") if err != nil { t.Fatalf("cannot generate keypair: %s\n", err) } diff --git a/engine/api/keys/parse.go b/engine/api/keys/parse.go index 0bd5b80aad..5167ccc91f 100644 --- a/engine/api/keys/parse.go +++ b/engine/api/keys/parse.go @@ -14,8 +14,10 @@ import ( type DecryptFunc func(context.Context, gorp.SqlExecutor, int64, string) (string, error) +type EmailFunc func(context.Context, string, string) (string, error) + // Parse and decrypts an exported key -func Parse(ctx context.Context, db gorp.SqlExecutor, projID int64, kname string, kval exportentities.KeyValue, decryptFunc DecryptFunc) (*sdk.Key, error) { +func Parse(ctx context.Context, db gorp.SqlExecutor, projID int64, kname string, kval exportentities.KeyValue, decryptFunc DecryptFunc, gpgEmail string) (*sdk.Key, error) { k := new(sdk.Key) k.Type = sdk.KeyType(kval.Type) k.Name = kname @@ -61,12 +63,23 @@ func Parse(ctx context.Context, db gorp.SqlExecutor, projID int64, kname string, default: return nil, sdk.WithStack(sdk.ErrUnknownKeyType) } - } else if kval.Regen == nil || *kval.Regen == true { - ktemp, err := GenerateKey(kname, k.Type) - if err != nil { - return nil, err + } else if kval.Regen == nil || *kval.Regen { + switch k.Type { + case sdk.KeyTypePGP: + ktemp, err := GeneratePGPKeyPair(kname, kname+" generated by CDS", gpgEmail) + if err != nil { + return nil, err + } + k = &ktemp + case sdk.KeyTypeSSH: + ktemp, err := GenerateSSHKey(kname) + if err != nil { + return nil, err + } + k = &ktemp + default: + return nil, sdk.WithStack(sdk.ErrUnknownKeyType) } - k = &ktemp } else { log.Debug(ctx, "keys.Parse> Skip key regeneration") } diff --git a/engine/api/project.go b/engine/api/project.go index 54df8fb2f9..a701bbaeee 100644 --- a/engine/api/project.go +++ b/engine/api/project.go @@ -567,7 +567,19 @@ func (api *API) postProjectHandler() service.Handler { k := &p.Keys[i] k.ProjectID = p.ID - newKey, err := keys.GenerateKey(k.Name, k.Type) + var newKey sdk.Key + var err error + switch k.Type { + case sdk.KeyTypePGP: + var email string + email, err = api.gpgKeyEmailAddress(ctx, p.Key, k.Name) + if err != nil { + return err + } + newKey, err = keys.GeneratePGPKeyPair(k.Name, "Project Key generated by CDS", email) + case sdk.KeyTypeSSH: + newKey, err = keys.GenerateSSHKey(k.Name) + } if err != nil { return err } diff --git a/engine/api/project/dao.go b/engine/api/project/dao.go index e3a66c7c39..bdb7d1e2ca 100644 --- a/engine/api/project/dao.go +++ b/engine/api/project/dao.go @@ -157,7 +157,7 @@ func Insert(db gorpmapper.SqlExecutorWithTx, proj *sdk.Project) error { } *proj = sdk.Project(dbProj) - k, err := keys.GeneratePGPKeyPair(BuiltinGPGKey) + k, err := keys.GeneratePGPKeyPair(BuiltinGPGKey, "builtin gpg key", proj.Name+"-builtin"+"@cds") if err != nil { return sdk.WrapError(err, "Unable to generate PGPKeyPair: %v", err) } diff --git a/engine/api/project_key.go b/engine/api/project_key.go index e2df172497..365e7566a5 100644 --- a/engine/api/project_key.go +++ b/engine/api/project_key.go @@ -1,9 +1,11 @@ package api import ( + "bytes" "context" "net/http" "strings" + "text/template" "github.com/gorilla/mux" @@ -70,6 +72,29 @@ func (api *API) deleteKeyInProjectHandler() service.Handler { } } +func (api *API) gpgKeyEmailAddress(ctx context.Context, projectKey, keyName string) (string, error) { + tmpl := api.Config.Project.GPGKeyEmailAddressTemplate + if tmpl == "" { + tmpl = "noreply+cds-{{.ProjectKey}}-{{.KeyName}}@localhost.local" + } + t, err := template.New("emailAddr").Parse(tmpl) + if err != nil { + return "", sdk.WrapError(err, "unable to parse template %q", tmpl) + } + var buf bytes.Buffer + err = t.Execute(&buf, struct { + ProjectKey string + KeyName string + }{ + projectKey, + keyName, + }) + if err != nil { + return "", sdk.WrapError(err, "unable to execute template %q", tmpl) + } + return buf.String(), nil +} + func (api *API) addKeyInProjectHandler() service.Handler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { vars := mux.Vars(r) @@ -96,10 +121,23 @@ func (api *API) addKeyInProjectHandler() service.Handler { newKey.Name = "proj-" + newKey.Name } - k, err := keys.GenerateKey(newKey.Name, newKey.Type) + var k sdk.Key + var err error + switch newKey.Type { + case sdk.KeyTypePGP: + var email string + email, err = api.gpgKeyEmailAddress(ctx, key, newKey.Name) + if err != nil { + return err + } + k, err = keys.GeneratePGPKeyPair(newKey.Name, "Project Key generated by CDS", email) // TODO email address + case sdk.KeyTypeSSH: + k, err = keys.GenerateSSHKey(newKey.Name) + } if err != nil { return err } + newKey.Private = k.Private newKey.Public = k.Public newKey.KeyID = k.KeyID diff --git a/engine/api/project_key_test.go b/engine/api/project_key_test.go index 7c1d4bd608..fb017ab8c7 100644 --- a/engine/api/project_key_test.go +++ b/engine/api/project_key_test.go @@ -31,7 +31,7 @@ func Test_getKeysInProjectHandler(t *testing.T) { ProjectID: proj.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k.Name) + kpgp, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") if err != nil { t.Fatal(err) } diff --git a/engine/api/repositories_manager_test.go b/engine/api/repositories_manager_test.go index e54f2cceb1..1f66b7be41 100644 --- a/engine/api/repositories_manager_test.go +++ b/engine/api/repositories_manager_test.go @@ -183,7 +183,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.Background(), db, api.Cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.Background(), db, api.Cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) proj, _ = project.LoadByID(db, proj.ID, project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments, project.LoadOptions.WithGroups) diff --git a/engine/api/templates.go b/engine/api/templates.go index d88c2198c3..775e0e8453 100644 --- a/engine/api/templates.go +++ b/engine/api/templates.go @@ -429,7 +429,7 @@ func (api *API) postTemplateApplyHandler() service.Handler { return service.Write(w, buf, http.StatusOK, "application/tar") } - msgs, wkf, oldWkf, _, err := workflow.Push(ctx, api.mustDB(), api.Cache, p, data, nil, consumer, project.DecryptWithBuiltinKey) + msgs, wkf, oldWkf, _, err := workflow.Push(ctx, api.mustDB(), api.Cache, p, data, nil, consumer, project.DecryptWithBuiltinKey, api.gpgKeyEmailAddress) if err != nil { return sdk.WrapError(err, "cannot push generated workflow") } diff --git a/engine/api/user_gpgkey_test.go b/engine/api/user_gpgkey_test.go index bbec1cef2d..4a613043f1 100644 --- a/engine/api/user_gpgkey_test.go +++ b/engine/api/user_gpgkey_test.go @@ -21,7 +21,7 @@ func Test_crudGPGKey(t *testing.T) { user1, pass := assets.InsertLambdaUser(t, db) - k, err := keys.GenerateKey(sdk.RandomString(10), sdk.KeyTypePGP) + k, err := keys.GeneratePGPKeyPair(sdk.RandomString(10), "", "test@cds") require.NoError(t, err) //------------ Create key diff --git a/engine/api/v2_project_vcs_test.go b/engine/api/v2_project_vcs_test.go index 006edd5348..59643829c4 100644 --- a/engine/api/v2_project_vcs_test.go +++ b/engine/api/v2_project_vcs_test.go @@ -3,14 +3,15 @@ package api import ( "context" "encoding/json" - "github.com/ovh/cds/engine/api/keys" - "github.com/ovh/cds/engine/api/project" "io" "net/http" "net/http/httptest" "strings" "testing" + "github.com/ovh/cds/engine/api/keys" + "github.com/ovh/cds/engine/api/project" + "github.com/go-gorp/gorp" "github.com/golang/mock/gomock" "github.com/ovh/cds/engine/api/services" @@ -54,7 +55,7 @@ func Test_crudVCSOnProjectLambdaUserOK(t *testing.T) { proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10)) user1, pass := assets.InsertLambdaUser(t, db) - newKey, err := keys.GenerateKey("mykey", sdk.KeyTypeSSH) + newKey, err := keys.GenerateSSHKey("mykey") require.NoError(t, err) k := sdk.ProjectKey{ Private: newKey.Private, @@ -139,7 +140,7 @@ func Test_crudVCSOnProjectAdminOk(t *testing.T) { u, pass := assets.InsertAdminUser(t, db) proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10)) - newKey, err := keys.GenerateKey("mykey", sdk.KeyTypeSSH) + newKey, err := keys.GenerateSSHKey("mykey") require.NoError(t, err) k := sdk.ProjectKey{ Private: newKey.Private, @@ -255,7 +256,7 @@ func Test_crudVCSOnPublicProject(t *testing.T) { proj := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10)) user1, pass := assets.InsertLambdaUser(t, db) - newKey, err := keys.GenerateKey("mykey", sdk.KeyTypeSSH) + newKey, err := keys.GenerateSSHKey("mykey") require.NoError(t, err) k := sdk.ProjectKey{ Private: newKey.Private, diff --git a/engine/api/workflow/dao.go b/engine/api/workflow/dao.go index c55f19489e..0d77d3aea8 100644 --- a/engine/api/workflow/dao.go +++ b/engine/api/workflow/dao.go @@ -1271,7 +1271,7 @@ func checkPipeline(ctx context.Context, db gorp.SqlExecutor, proj sdk.Project, w // Push push a workflow from cds files func Push(ctx context.Context, db *gorp.DbMap, store cache.Store, proj *sdk.Project, data exportentities.WorkflowComponents, - opts *PushOption, consumer *sdk.AuthUserConsumer, decryptFunc keys.DecryptFunc) ([]sdk.Message, *sdk.Workflow, *sdk.Workflow, *PushSecrets, error) { + opts *PushOption, consumer *sdk.AuthUserConsumer, decryptFunc keys.DecryptFunc, emailFunc keys.EmailFunc) ([]sdk.Message, *sdk.Workflow, *sdk.Workflow, *PushSecrets, error) { ctx, end := telemetry.Span(ctx, "workflow.Push") defer end() if data.Workflow == nil { @@ -1327,7 +1327,7 @@ func Push(ctx context.Context, db *gorp.DbMap, store cache.Store, proj *sdk.Proj EnvironmentdSecrets: make(map[int64][]sdk.Variable), } for _, app := range data.Applications { - appDB, appSecrets, msgList, err := application.ParseAndImport(ctx, tx, store, *proj, &app, application.ImportOptions{Force: true, FromRepository: fromRepo}, decryptFunc, consumer) + appDB, appSecrets, msgList, err := application.ParseAndImport(ctx, tx, store, *proj, &app, application.ImportOptions{Force: true, FromRepository: fromRepo}, decryptFunc, consumer, emailFunc) allMsg = append(allMsg, msgList...) if err != nil { return allMsg, nil, nil, nil, sdk.ErrorWithFallback(err, sdk.ErrWrongRequest, "unable to import application %s/%s", proj.Key, app.Name) @@ -1337,7 +1337,7 @@ func Push(ctx context.Context, db *gorp.DbMap, store cache.Store, proj *sdk.Proj } for _, env := range data.Environments { - envDB, envsSecrets, msgList, err := environment.ParseAndImport(ctx, tx, *proj, env, environment.ImportOptions{Force: true, FromRepository: fromRepo}, decryptFunc, consumer) + envDB, envsSecrets, msgList, err := environment.ParseAndImport(ctx, tx, *proj, env, environment.ImportOptions{Force: true, FromRepository: fromRepo}, decryptFunc, consumer, emailFunc) allMsg = append(allMsg, msgList...) if err != nil { return allMsg, nil, nil, nil, sdk.ErrorWithFallback(err, sdk.ErrWrongRequest, "unable to import environment %s/%s", proj.Key, env.Name) diff --git a/engine/api/workflow/dao_node_run_test.go b/engine/api/workflow/dao_node_run_test.go index 98fa78d5c6..9dff554adb 100644 --- a/engine/api/workflow/dao_node_run_test.go +++ b/engine/api/workflow/dao_node_run_test.go @@ -181,7 +181,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) // Add application2 @@ -193,7 +193,7 @@ vcs_ssh_key: proj-blabla ` var eapp2 = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS2), eapp2)) - app2, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp2, application.ImportOptions{Force: true}, nil, u) + app2, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp2, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) proj, _ = project.LoadByID(db, proj.ID, project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments, project.LoadOptions.WithGroups) diff --git a/engine/api/workflow/dao_run_test.go b/engine/api/workflow/dao_run_test.go index ba507e38b8..bcf30217b0 100644 --- a/engine/api/workflow/dao_run_test.go +++ b/engine/api/workflow/dao_run_test.go @@ -187,7 +187,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) proj, _ = project.LoadByID(db, proj.ID, project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments, project.LoadOptions.WithGroups) @@ -451,7 +451,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) proj, _ = project.LoadByID(db, proj.ID, project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments, project.LoadOptions.WithGroups) @@ -642,7 +642,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.TODO(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) proj, _ = project.LoadByID(db, proj.ID, project.LoadOptions.WithApplications, project.LoadOptions.WithPipelines, project.LoadOptions.WithEnvironments, project.LoadOptions.WithGroups) diff --git a/engine/api/workflow/dao_test.go b/engine/api/workflow/dao_test.go index 01c77a2888..84c3855ecd 100644 --- a/engine/api/workflow/dao_test.go +++ b/engine/api/workflow/dao_test.go @@ -1798,7 +1798,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) proj.Applications = append(proj.Applications, *app) @@ -2067,7 +2067,7 @@ vcs_ssh_key: proj-blabla` var eapp = new(exportentities.Application) require.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, err := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{FromRepository: "from/my-repo"}, nil, u) + app, _, _, err := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{FromRepository: "from/my-repo"}, nil, u, nil) require.NotNil(t, app) require.NoError(t, err) @@ -2080,7 +2080,7 @@ values: var eEnv = new(exportentities.Environment) require.NoError(t, yaml.Unmarshal([]byte(envS), eEnv)) - env, _, _, err := environment.ParseAndImport(ctx, db, *proj, *eEnv, environment.ImportOptions{FromRepository: "from/my-repo"}, project.DecryptWithBuiltinKey, u) + env, _, _, err := environment.ParseAndImport(ctx, db, *proj, *eEnv, environment.ImportOptions{FromRepository: "from/my-repo"}, project.DecryptWithBuiltinKey, u, nil) require.NotNil(t, env) require.NoError(t, err) @@ -2262,7 +2262,7 @@ vcs_ssh_key: proj-blabla` var eapp = new(exportentities.Application) require.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, err := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{FromRepository: "from/my-repo"}, nil, u) + app, _, _, err := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{FromRepository: "from/my-repo"}, nil, u, nil) require.NotNil(t, app) require.NoError(t, err) @@ -2275,7 +2275,7 @@ values: var eEnv = new(exportentities.Environment) require.NoError(t, yaml.Unmarshal([]byte(envS), eEnv)) - env, _, _, err := environment.ParseAndImport(ctx, db, *proj, *eEnv, environment.ImportOptions{FromRepository: "from/my-repo"}, project.DecryptWithBuiltinKey, u) + env, _, _, err := environment.ParseAndImport(ctx, db, *proj, *eEnv, environment.ImportOptions{FromRepository: "from/my-repo"}, project.DecryptWithBuiltinKey, u, nil) require.NotNil(t, env) require.NoError(t, err) diff --git a/engine/api/workflow/process_node_test.go b/engine/api/workflow/process_node_test.go index 088d2750ea..ba98eaa926 100644 --- a/engine/api/workflow/process_node_test.go +++ b/engine/api/workflow/process_node_test.go @@ -2981,7 +2981,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) return app } @@ -2996,7 +2996,7 @@ vcs_ssh_key: proj-bloublou ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) return app } @@ -3011,7 +3011,7 @@ vcs_ssh_key: proj-blabla ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) return app } @@ -3023,7 +3023,7 @@ name: app-no-repo ` var eapp = new(exportentities.Application) assert.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u) + app, _, _, globalError := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{Force: true}, nil, u, nil) assert.NoError(t, globalError) return app } diff --git a/engine/api/workflow/repository.go b/engine/api/workflow/repository.go index 609a3ba033..0f47f2e944 100644 --- a/engine/api/workflow/repository.go +++ b/engine/api/workflow/repository.go @@ -37,7 +37,7 @@ type PushOption struct { // CreateFromRepository a workflow from a repository. func CreateFromRepository(ctx context.Context, db *gorp.DbMap, store cache.Store, p *sdk.Project, wf *sdk.Workflow, - opts sdk.WorkflowRunPostHandlerOption, u sdk.AuthUserConsumer, decryptFunc keys.DecryptFunc) (*PushSecrets, []sdk.Message, error) { + opts sdk.WorkflowRunPostHandlerOption, u sdk.AuthUserConsumer, decryptFunc keys.DecryptFunc, emailFunc keys.EmailFunc) (*PushSecrets, []sdk.Message, error) { ctx, end := telemetry.Span(ctx, "workflow.CreateFromRepository") defer end() @@ -78,11 +78,11 @@ func CreateFromRepository(ctx context.Context, db *gorp.DbMap, store cache.Store } } } - return extractWorkflow(ctx, db, store, p, wf, *ope, u, decryptFunc, uuid) + return extractWorkflow(ctx, db, store, p, wf, *ope, u, decryptFunc, uuid, emailFunc) } func extractWorkflow(ctx context.Context, db *gorp.DbMap, store cache.Store, p *sdk.Project, wf *sdk.Workflow, - ope sdk.Operation, consumer sdk.AuthUserConsumer, decryptFunc keys.DecryptFunc, hookUUID string) (*PushSecrets, []sdk.Message, error) { + ope sdk.Operation, consumer sdk.AuthUserConsumer, decryptFunc keys.DecryptFunc, hookUUID string, emailFunc keys.EmailFunc) (*PushSecrets, []sdk.Message, error) { ctx, end := telemetry.Span(ctx, "workflow.extractWorkflow") defer end() var allMsgs []sdk.Message @@ -125,7 +125,7 @@ func extractWorkflow(ctx context.Context, db *gorp.DbMap, store cache.Store, p * if err != nil { return nil, allMsgs, err } - msgPush, workflowPushed, _, secrets, err := Push(ctx, db, store, p, data, opt, &consumer, decryptFunc) + msgPush, workflowPushed, _, secrets, err := Push(ctx, db, store, p, data, opt, &consumer, decryptFunc, emailFunc) // Filter workflow push message if generated from template for i := range msgPush { if wti != nil && msgPush[i].ID == sdk.MsgWorkflowDeprecatedVersion.ID { diff --git a/engine/api/workflow_ascode_test.go b/engine/api/workflow_ascode_test.go index 2a7ed803de..67c199ea4d 100644 --- a/engine/api/workflow_ascode_test.go +++ b/engine/api/workflow_ascode_test.go @@ -1200,7 +1200,7 @@ hooks: // Import Application var eapp exportentities.Application require.NoError(t, err, yaml.Unmarshal([]byte(app), &eapp)) - appDB, _, _, err := application.ParseAndImport(context.TODO(), db, api.Cache, *proj, &eapp, application.ImportOptions{Force: true, FromRepository: "https://github.com/fsamin/go-repo.git"}, nil, u) + appDB, _, _, err := application.ParseAndImport(context.TODO(), db, api.Cache, *proj, &eapp, application.ImportOptions{Force: true, FromRepository: "https://github.com/fsamin/go-repo.git"}, nil, u, nil) require.NoError(t, err) require.Equal(t, appDB.FromRepository, "https://github.com/fsamin/go-repo.git") @@ -1222,7 +1222,7 @@ hooks: c := sdk.AuthUserConsumer{ AuthConsumerUser: sdk.AuthUserConsumerData{AuthentifiedUser: u}, } - _, _, err = workflow.CreateFromRepository(context.TODO(), api.mustDB(), api.Cache, proj, workflowInserted, opts, c, project.DecryptWithBuiltinKey) + _, _, err = workflow.CreateFromRepository(context.TODO(), api.mustDB(), api.Cache, proj, workflowInserted, opts, c, project.DecryptWithBuiltinKey, api.gpgKeyEmailAddress) require.NoError(t, err) } diff --git a/engine/api/workflow_import.go b/engine/api/workflow_import.go index 16d8cddebc..2c5e7e8171 100644 --- a/engine/api/workflow_import.go +++ b/engine/api/workflow_import.go @@ -334,7 +334,7 @@ func (api *API) postWorkflowPushHandler() service.Handler { if err != nil { return err } - msgPush, wrkflw, oldWrkflw, _, err := workflow.Push(ctx, db, api.Cache, proj, data, pushOptions, consumer, project.DecryptWithBuiltinKey) + msgPush, wrkflw, oldWrkflw, _, err := workflow.Push(ctx, db, api.Cache, proj, data, pushOptions, consumer, project.DecryptWithBuiltinKey, api.gpgKeyEmailAddress) allMsg = append(allMsg, msgPush...) if err != nil { return err diff --git a/engine/api/workflow_import_test.go b/engine/api/workflow_import_test.go index cdfa668275..844408d9c8 100644 --- a/engine/api/workflow_import_test.go +++ b/engine/api/workflow_import_test.go @@ -752,7 +752,7 @@ func Test_getWorkflowPushHandler(t *testing.T) { ApplicationID: app.ID, } - kpgp, err := keys.GeneratePGPKeyPair(k.Name) + kpgp, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") require.NoError(t, err) k.Public = kpgp.Public diff --git a/engine/api/workflow_queue_test.go b/engine/api/workflow_queue_test.go index f036c9f687..40c26ffedb 100644 --- a/engine/api/workflow_queue_test.go +++ b/engine/api/workflow_queue_test.go @@ -78,7 +78,16 @@ func testRunWorkflow(t *testing.T, api *API, router *Router, optsF ...testRunWor for i := range proj.Keys { k := &proj.Keys[i] k.ProjectID = proj.ID - newKey, err := keys.GenerateKey(k.Name, k.Type) + + var newKey sdk.Key + var err error + switch k.Type { + case sdk.KeyTypePGP: + newKey, err = keys.GeneratePGPKeyPair(k.Name, "", "test@cds") + case sdk.KeyTypeSSH: + newKey, err = keys.GenerateSSHKey(k.Name) + } + require.NoError(t, err) k.Private = newKey.Private k.Public = newKey.Public @@ -151,7 +160,7 @@ func testRunWorkflowForProject(t *testing.T, api *API, router *Router, proj *sdk ApplicationID: app.ID, } - pgpK, err := keys.GeneratePGPKeyPair(k.Name) + pgpK, err := keys.GeneratePGPKeyPair(k.Name, "", "test@cds") require.NoError(t, err) k.Public = pgpK.Public @@ -173,7 +182,7 @@ func testRunWorkflowForProject(t *testing.T, api *API, router *Router, proj *sdk EnvironmentID: env.ID, } - kpgp, err := keys.GeneratePGPKeyPair(envk.Name) + kpgp, err := keys.GeneratePGPKeyPair(envk.Name, "", "test@cds") require.NoError(t, err) envk.Public = kpgp.Public diff --git a/engine/api/workflow_run.go b/engine/api/workflow_run.go index 0aa17323ce..ea95da0727 100644 --- a/engine/api/workflow_run.go +++ b/engine/api/workflow_run.go @@ -797,7 +797,7 @@ func (api *API) getWorkflowNodeRunHandler() service.Handler { return err } nodeRun, err := workflow.LoadNodeRun(api.mustDB(), key, name, id, workflow.LoadRunOptions{ - WithTests: true, + WithTests: true, }) if err != nil { return sdk.WrapError(err, "Unable to load last workflow run") @@ -1065,7 +1065,7 @@ func (api *API) initWorkflowRun(ctx context.Context, projKey string, wf *sdk.Wor log.Debug(ctx, "workflow.CreateFromRepository> %s", wf.Name) oldWf := *wf var asCodeInfosMsg []sdk.Message - workflowSecrets, asCodeInfosMsg, err = workflow.CreateFromRepository(ctx, api.mustDB(), api.Cache, p1, wf, opts, *c, project.DecryptWithBuiltinKey) + workflowSecrets, asCodeInfosMsg, err = workflow.CreateFromRepository(ctx, api.mustDB(), api.Cache, p1, wf, opts, *c, project.DecryptWithBuiltinKey, api.gpgKeyEmailAddress) infos := make([]sdk.SpawnMsg, len(asCodeInfosMsg)) for i, msg := range asCodeInfosMsg { infos[i] = msg.ToSpawnMsg() diff --git a/engine/api/workflow_template_bulk.go b/engine/api/workflow_template_bulk.go index 6adff10674..752392a049 100644 --- a/engine/api/workflow_template_bulk.go +++ b/engine/api/workflow_template_bulk.go @@ -322,7 +322,7 @@ func (api *API) workflowTemplateBulkOperation(ctx context.Context, op WorkflowTe return } - _, wkf, _, _, err := workflow.Push(ctx, api.mustDB(), api.Cache, p, data, nil, consumer, project.DecryptWithBuiltinKey) + _, wkf, _, _, err := workflow.Push(ctx, api.mustDB(), api.Cache, p, data, nil, consumer, project.DecryptWithBuiltinKey, api.gpgKeyEmailAddress) if err != nil { if sdk.ErrorIs(err, sdk.ErrEnvironmentExist) { err = sdk.NewErrorFrom(err, "conflict when creating an environment with same name on multiple workflow") diff --git a/engine/api/workflow_test.go b/engine/api/workflow_test.go index d36bb948be..ceb3ae983d 100644 --- a/engine/api/workflow_test.go +++ b/engine/api/workflow_test.go @@ -2107,7 +2107,7 @@ vcs_ssh_key: proj-blabla` var eapp = new(exportentities.Application) require.NoError(t, yaml.Unmarshal([]byte(appS), eapp)) - app, _, _, err := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{FromRepository: "from/my-repo"}, nil, u) + app, _, _, err := application.ParseAndImport(context.Background(), db, cache, *proj, eapp, application.ImportOptions{FromRepository: "from/my-repo"}, nil, u, nil) require.NotNil(t, app) require.NoError(t, err) @@ -2120,7 +2120,7 @@ values: var eEnv = new(exportentities.Environment) require.NoError(t, yaml.Unmarshal([]byte(envS), eEnv)) - env, _, _, err := environment.ParseAndImport(ctx, db, *proj, *eEnv, environment.ImportOptions{FromRepository: "from/my-repo"}, project.DecryptWithBuiltinKey, u) + env, _, _, err := environment.ParseAndImport(ctx, db, *proj, *eEnv, environment.ImportOptions{FromRepository: "from/my-repo"}, project.DecryptWithBuiltinKey, u, nil) require.NotNil(t, env) require.NoError(t, err) From a3a89f175540a87547cb54d154fe0f1dec414c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Samin?= Date: Fri, 17 Nov 2023 14:03:39 +0100 Subject: [PATCH 2/3] feat(api): valid email addresses for GPG keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: François Samin --- engine/api/workflowtemplate/dao_instance_test.go | 1 + engine/api/workflowtemplate/instance_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/engine/api/workflowtemplate/dao_instance_test.go b/engine/api/workflowtemplate/dao_instance_test.go index 512a604a56..bc1ef44729 100644 --- a/engine/api/workflowtemplate/dao_instance_test.go +++ b/engine/api/workflowtemplate/dao_instance_test.go @@ -118,6 +118,7 @@ func TestLoad_Instance(t *testing.T) { i, err = workflowtemplate.LoadInstanceByIDForTemplateIDAndProjectIDs(context.TODO(), db, wti2.ID, tmpl.ID, []int64{proj1.ID}) require.Error(t, err) + assert.NotNil(t, i) i, err = workflowtemplate.LoadInstanceByIDForTemplateIDAndProjectIDs(context.TODO(), db, wti2.ID, tmpl.ID, []int64{proj2.ID}) require.NoError(t, err) diff --git a/engine/api/workflowtemplate/instance_test.go b/engine/api/workflowtemplate/instance_test.go index f9d610f3e1..66d6d606c6 100644 --- a/engine/api/workflowtemplate/instance_test.go +++ b/engine/api/workflowtemplate/instance_test.go @@ -218,7 +218,7 @@ name: Pipeline-[[.id]]`)), _, wti, err := workflowtemplate.CheckAndExecuteTemplate(context.TODO(), db.DbMap, cache, *consumer, *proj, &data) require.NoError(t, err) - _, wkf, _, _, err := workflow.Push(context.TODO(), db.DbMap, cache, proj, data, nil, consumer, project.DecryptWithBuiltinKey) + _, wkf, _, _, err := workflow.Push(context.TODO(), db.DbMap, cache, proj, data, nil, consumer, project.DecryptWithBuiltinKey, nil) require.NoError(t, err) require.NoError(t, workflowtemplate.UpdateTemplateInstanceWithWorkflow(context.TODO(), db, *wkf, consumer, wti)) From d4172a9062363742c22165084108d78000b7a25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Samin?= Date: Fri, 17 Nov 2023 14:20:43 +0100 Subject: [PATCH 3/3] feat(api): valid email addresses for GPG keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: François Samin --- engine/api/workflowtemplate/dao_instance_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/api/workflowtemplate/dao_instance_test.go b/engine/api/workflowtemplate/dao_instance_test.go index bc1ef44729..bc34ee6814 100644 --- a/engine/api/workflowtemplate/dao_instance_test.go +++ b/engine/api/workflowtemplate/dao_instance_test.go @@ -118,7 +118,7 @@ func TestLoad_Instance(t *testing.T) { i, err = workflowtemplate.LoadInstanceByIDForTemplateIDAndProjectIDs(context.TODO(), db, wti2.ID, tmpl.ID, []int64{proj1.ID}) require.Error(t, err) - assert.NotNil(t, i) + assert.Nil(t, i) i, err = workflowtemplate.LoadInstanceByIDForTemplateIDAndProjectIDs(context.TODO(), db, wti2.ID, tmpl.ID, []int64{proj2.ID}) require.NoError(t, err)