diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index 408f47d40e..33d0e261c1 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -461,6 +461,28 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error { b.Database.LogMode(true) + if err := b.CreateGlobalKmsKeys(); err != nil { + return err + } + + if opts.withSkipAuthMethodCreation { + // now that we have passed all the error cases, reset c to be a noop so the + // defer doesn't do anything. + c = func() error { return nil } + return nil + } + + if err := b.CreateInitialAuthMethod(); err != nil { + return err + } + + // now that we have passed all the error cases, reset c to be a noop so the + // defer doesn't do anything. + c = func() error { return nil } + return nil +} + +func (b *Server) CreateGlobalKmsKeys() error { rw := db.New(b.Database) kmsRepo, err := kms.NewRepository(rw, rw) @@ -488,11 +510,24 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error { return fmt.Errorf("error creating global scope kms keys: %w", err) } - if opts.withSkipAuthMethodCreation { - // now that we have passed all the error cases, reset c to be a noop so the - // defer doesn't do anything. - c = func() error { return nil } - return nil + return nil +} + +func (b *Server) CreateInitialAuthMethod() error { + rw := db.New(b.Database) + + kmsRepo, err := kms.NewRepository(rw, rw) + if err != nil { + return fmt.Errorf("error creating kms repository: %w", err) + } + kmsCache, err := kms.NewKms(kmsRepo) + if err != nil { + return fmt.Errorf("error creating kms cache: %w", err) + } + if err := kmsCache.AddExternalWrappers( + kms.WithRootWrapper(b.RootKms), + ); err != nil { + return fmt.Errorf("error adding config keys to kms: %w", err) } // Create the dev auth method @@ -504,46 +539,53 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error { if err != nil { return fmt.Errorf("error creating new in memory auth method: %w", err) } - amId := b.DevAuthMethodId - if amId == "" { - amId = "ampw_1234567890" + if b.DevAuthMethodId == "" { + b.DevAuthMethodId, err = db.NewPublicId(password.AuthMethodPrefix) + if err != nil { + return fmt.Errorf("error generating dev auth method id: %w", err) + } } - _, err = pwRepo.CreateAuthMethod(ctx, authMethod, password.WithPublicId(amId)) + + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-b.ShutdownCh + cancel() + }() + + _, err = pwRepo.CreateAuthMethod(ctx, authMethod, password.WithPublicId(b.DevAuthMethodId)) if err != nil { return fmt.Errorf("error saving auth method to the db: %w", err) } - b.InfoKeys = append(b.InfoKeys, "dev auth method id") - b.Info["dev auth method id"] = amId + b.InfoKeys = append(b.InfoKeys, "generated auth method id") + b.Info["generated auth method id"] = b.DevAuthMethodId // Create the dev user - acctLoginName := b.DevLoginName - if acctLoginName == "" { - acctLoginName, err = base62.Random(10) + if b.DevLoginName == "" { + b.DevLoginName, err = base62.Random(10) if err != nil { - return fmt.Errorf("unable to generate dev login name: %w", err) + return fmt.Errorf("unable to generate login name: %w", err) } - acctLoginName = strings.ToLower(acctLoginName) + b.DevLoginName = strings.ToLower(b.DevLoginName) } - pw := b.DevPassword - if pw == "" { - pw, err = base62.Random(20) + if b.DevPassword == "" { + b.DevPassword, err = base62.Random(20) if err != nil { - return fmt.Errorf("unable to generate dev password: %w", err) + return fmt.Errorf("unable to generate password: %w", err) } } - b.InfoKeys = append(b.InfoKeys, "dev password") - b.Info["dev password"] = pw + b.InfoKeys = append(b.InfoKeys, "generated password") + b.Info["generated password"] = b.DevPassword - acct, err := password.NewAccount(amId, password.WithLoginName(acctLoginName)) + acct, err := password.NewAccount(b.DevAuthMethodId, password.WithLoginName(b.DevLoginName)) if err != nil { return fmt.Errorf("error creating new in memory auth account: %w", err) } - acct, err = pwRepo.CreateAccount(ctx, scope.Global.String(), acct, password.WithPassword(pw)) + acct, err = pwRepo.CreateAccount(ctx, scope.Global.String(), acct, password.WithPassword(b.DevPassword)) if err != nil { return fmt.Errorf("error saving auth account to the db: %w", err) } - b.InfoKeys = append(b.InfoKeys, "dev login name") - b.Info["dev login name"] = acct.GetLoginName() + b.InfoKeys = append(b.InfoKeys, "generated login name") + b.Info["generated login name"] = acct.GetLoginName() // Create a role tying them together iamRepo, err := iam.NewRepository(rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader)) @@ -552,24 +594,21 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error { } pr, err := iam.NewRole(scope.Global.String()) if err != nil { - return fmt.Errorf("error creating in memory role for default dev grants: %w", err) + return fmt.Errorf("error creating in memory role for generated grants: %w", err) } - pr.Name = "Dev Mode Global Scope Admin Role" + pr.Name = "Generated Global Scope Admin Role" pr.Description = `Provides admin grants to all authenticated users within the "global" scope` defPermsRole, err := iamRepo.CreateRole(ctx, pr) if err != nil { - return fmt.Errorf("error creating role for default dev grants: %w", err) + return fmt.Errorf("error creating role for default generated grants: %w", err) } if _, err := iamRepo.AddRoleGrants(ctx, defPermsRole.PublicId, defPermsRole.Version, []string{"id=*;actions=*"}); err != nil { - return fmt.Errorf("error creating grant for default dev grants: %w", err) + return fmt.Errorf("error creating grant for default generated grants: %w", err) } if _, err := iamRepo.AddPrincipalRoles(ctx, defPermsRole.PublicId, defPermsRole.Version+1, []string{"u_auth"}, nil); err != nil { - return fmt.Errorf("error adding principal to role for default dev grants: %w", err) + return fmt.Errorf("error adding principal to role for default generated grants: %w", err) } - // now that we have passed all the error cases, reset c to be a noop so the - // defer doesn't do anything. - c = func() error { return nil } return nil } diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 2746ca7a81..88d33084ef 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -35,6 +35,7 @@ type Command struct { flagDevControllerAPIListenAddr string flagDevControllerClusterListenAddr string flagDevSkipAuthMethodCreation bool + flagDevDisableDatabaseDestruction bool } func (c *Command) Synopsis() string { @@ -124,6 +125,12 @@ func (c *Command) Flags() *base.FlagSets { Usage: "If set, an auth method will not be created as part of the dev instance. The recovery KMS will be needed to perform any actions.", }) + f.BoolVar(&base.BoolVar{ + Name: "dev-disable-database-destruction", + Target: &c.flagDevDisableDatabaseDestruction, + Usage: "If set, if a database is created automatically in Docker, it will not be removed when the dev server is shut down.", + }) + f.BoolVar(&base.BoolVar{ Name: "combine-logs", Target: &c.flagCombineLogs, @@ -266,7 +273,9 @@ func (c *Command) Run(args []string) int { c.UI.Error(fmt.Errorf("Error creating dev database container: %w", err).Error()) return 1 } - c.ShutdownFuncs = append(c.ShutdownFuncs, c.DestroyDevDatabase) + if !c.flagDevDisableDatabaseDestruction { + c.ShutdownFuncs = append(c.ShutdownFuncs, c.DestroyDevDatabase) + } c.PrintInfo(c.UI, "dev mode") c.ReleaseLogGate() diff --git a/internal/servers/controller/handler_test.go b/internal/servers/controller/handler_test.go index 7fadc46cc7..ddd2b0b537 100644 --- a/internal/servers/controller/handler_test.go +++ b/internal/servers/controller/handler_test.go @@ -17,6 +17,7 @@ import ( func TestAuthenticationHandler(t *testing.T) { c := NewTestController(t, &TestControllerOpts{ DisableAuthorizationFailures: true, + DefaultAuthMethodId: "ampw_1234567890", DefaultLoginName: "admin", DefaultPassword: "password123", }) diff --git a/internal/servers/controller/testing.go b/internal/servers/controller/testing.go index 3dfd707bca..f01a13c526 100644 --- a/internal/servers/controller/testing.go +++ b/internal/servers/controller/testing.go @@ -194,9 +194,11 @@ func (tc *TestController) Shutdown() { if err := tc.b.RunShutdownFuncs(); err != nil { tc.t.Error(err) } - if tc.b.DestroyDevDatabase() != nil { - if err := tc.b.DestroyDevDatabase(); err != nil { - tc.t.Error(err) + if !tc.opts.DisableDatabaseDestruction { + if tc.b.DestroyDevDatabase() != nil { + if err := tc.b.DestroyDevDatabase(); err != nil { + tc.t.Error(err) + } } } } @@ -223,6 +225,10 @@ type TestControllerOpts struct { // database DisableDatabaseCreation bool + // DisableDatabaseDestruction can be set true to allow a database to be + // created but examined after-the-fact + DisableDatabaseDestruction bool + // If set, instead of creating a dev database, it will connect to an // existing database given the url DatabaseUrl string @@ -350,6 +356,14 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController { if err := tc.b.ConnectToDatabase("postgres"); err != nil { t.Fatal(err) } + if err := tc.b.CreateGlobalKmsKeys(); err != nil { + t.Fatal(err) + } + if !opts.DisableAuthMethodCreation { + if err := tc.b.CreateInitialAuthMethod(); err != nil { + t.Fatal(err) + } + } } else if !opts.DisableDatabaseCreation { var createOpts []base.Option if opts.DisableAuthMethodCreation { diff --git a/testing/controller/controller.go b/testing/controller/controller.go index 342a86658e..491c244c2b 100644 --- a/testing/controller/controller.go +++ b/testing/controller/controller.go @@ -39,18 +39,19 @@ func getOpts(opt ...Option) (*controller.TestControllerOpts, error) { } type option struct { - tcOptions *controller.TestControllerOpts - setWithConfigFile bool - setWithConfigText bool - setDisableAuthMethodCreation bool - setDisableDatabaseCreation bool - setDefaultAuthMethodId bool - setDefaultLoginName bool - setDefaultPassword bool - setRootKms bool - setWorkerAuthKms bool - setRecoveryKms bool - setDatabaseUrl bool + tcOptions *controller.TestControllerOpts + setWithConfigFile bool + setWithConfigText bool + setDisableAuthMethodCreation bool + setDisableDatabaseCreation bool + setDisableDatabaseDesctruction bool + setDefaultAuthMethodId bool + setDefaultLoginName bool + setDefaultPassword bool + setRootKms bool + setWorkerAuthKms bool + setRecoveryKms bool + setDatabaseUrl bool } type Option func(*option) error @@ -108,6 +109,16 @@ func DisableDatabaseCreation() Option { } } +// DisableDatabaseCreation skips creating a database in docker and allows one to +// be provided through a tcOptions. +func DisableDatabaseDestruction() Option { + return func(c *option) error { + c.setDisableDatabaseDestruction = true + c.tcOptions.DisableDatabaseDestruction = true + return nil + } +} + func WithDefaultAuthMethodId(id string) Option { return func(c *option) error { c.setDefaultAuthMethodId = true