From 31fe9292b389835bda265a87632b9bea33868bad Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 24 Sep 2020 03:31:26 -0400 Subject: [PATCH] Instantiate default resources (#425) --- internal/cmd/base/initial_resources.go | 386 ++++++++++++++++++ internal/cmd/base/option.go | 41 +- internal/cmd/base/servers.go | 162 +++----- internal/cmd/commands/database/funcs.go | 108 ++++- internal/cmd/commands/database/init.go | 179 ++++++-- internal/cmd/commands/dev/dev.go | 102 ++--- internal/host/static/options.go | 8 + internal/host/static/options_test.go | 6 + internal/host/static/repository_host.go | 17 +- .../host/static/repository_host_catalog.go | 17 +- internal/host/static/repository_host_set.go | 17 +- internal/iam/repository_scope.go | 2 +- internal/iam/repository_user.go | 25 +- .../handlers/scopes/scope_service_test.go | 2 +- internal/servers/controller/testing.go | 35 +- internal/target/options.go | 9 + internal/target/options_test.go | 7 + internal/target/repository_tcp_target.go | 20 +- 18 files changed, 904 insertions(+), 239 deletions(-) create mode 100644 internal/cmd/base/initial_resources.go diff --git a/internal/cmd/base/initial_resources.go b/internal/cmd/base/initial_resources.go new file mode 100644 index 000000000..dd879d925 --- /dev/null +++ b/internal/cmd/base/initial_resources.go @@ -0,0 +1,386 @@ +package base + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/auth/password" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/vault/sdk/helper/base62" +) + +func (b *Server) CreateInitialAuthMethod(ctx context.Context) 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, kms.WithLogger(b.Logger.Named("kms"))) + 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 + pwRepo, err := password.NewRepository(rw, rw, kmsCache) + if err != nil { + return fmt.Errorf("error creating password repo: %w", err) + } + authMethod, err := password.NewAuthMethod(scope.Global.String()) + if err != nil { + return fmt.Errorf("error creating new in memory auth method: %w", err) + } + if b.DevAuthMethodId == "" { + b.DevAuthMethodId, err = db.NewPublicId(password.AuthMethodPrefix) + if err != nil { + return fmt.Errorf("error generating initial auth method id: %w", err) + } + } + + cancelCtx, cancel := context.WithCancel(ctx) + go func() { + <-b.ShutdownCh + cancel() + }() + + _, err = pwRepo.CreateAuthMethod(cancelCtx, authMethod, + password.WithName("Generated global scope initial auth method"), + password.WithDescription("Provides initial administrative authentication into Boundary"), + password.WithPublicId(b.DevAuthMethodId)) + if err != nil { + return fmt.Errorf("error saving auth method to the db: %w", err) + } + b.InfoKeys = append(b.InfoKeys, "generated auth method id") + b.Info["generated auth method id"] = b.DevAuthMethodId + + // Create the dev user + if b.DevLoginName == "" { + b.DevLoginName, err = base62.Random(10) + if err != nil { + return fmt.Errorf("unable to generate login name: %w", err) + } + b.DevLoginName = strings.ToLower(b.DevLoginName) + } + if b.DevPassword == "" { + b.DevPassword, err = base62.Random(20) + if err != nil { + return fmt.Errorf("unable to generate password: %w", err) + } + } + b.InfoKeys = append(b.InfoKeys, "generated auth method password") + b.Info["generated auth method password"] = b.DevPassword + + 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(cancelCtx, 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, "generated auth method login name") + b.Info["generated auth method login name"] = acct.GetLoginName() + + iamRepo, err := iam.NewRepository(rw, rw, kmsCache, iam.WithRandomReader(b.SecureRandomReader)) + if err != nil { + return fmt.Errorf("unable to create repo for org id: %w", err) + } + + // Create a new user and associate it with the account + if b.DevUserId == "" { + b.DevUserId, err = db.NewPublicId(iam.UserPrefix) + if err != nil { + return fmt.Errorf("error generating initial user id: %w", err) + } + } + opts := []iam.Option{ + iam.WithName("admin"), + iam.WithDescription(`Initial admin user within the "global" scope`), + iam.WithPublicId(b.DevUserId), + } + u, err := iam.NewUser(scope.Global.String(), opts...) + if err != nil { + return fmt.Errorf("error creating in memory user: %w", err) + } + if u, err = iamRepo.CreateUser(cancelCtx, u, opts...); err != nil { + return fmt.Errorf("error creating initial admin user: %w", err) + } + if u, err = iamRepo.AssociateUserWithAccount(cancelCtx, u.GetPublicId(), acct.GetPublicId()); err != nil { + return fmt.Errorf("error associating initial admin user with account: %w", err) + } + + // Create a role tying them together + pr, err := iam.NewRole(scope.Global.String(), + iam.WithName("Generated global scope admin role"), + iam.WithDescription(`Provides admin grants within the "global" scope to the initial user`), + ) + if err != nil { + return fmt.Errorf("error creating in memory role for generated grants: %w", err) + } + defPermsRole, err := iamRepo.CreateRole(cancelCtx, pr) + if err != nil { + return fmt.Errorf("error creating role for default generated grants: %w", err) + } + if _, err := iamRepo.AddRoleGrants(cancelCtx, defPermsRole.PublicId, defPermsRole.Version, []string{"id=*;actions=*"}); err != nil { + return fmt.Errorf("error creating grant for default generated grants: %w", err) + } + if _, err := iamRepo.AddPrincipalRoles(cancelCtx, defPermsRole.PublicId, defPermsRole.Version+1, []string{u.GetPublicId()}, nil); err != nil { + return fmt.Errorf("error adding principal to role for default generated grants: %w", err) + } + + return nil +} + +func (b *Server) CreateInitialScopes(ctx context.Context) 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, kms.WithLogger(b.Logger.Named("kms"))) + 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) + } + + cancelCtx, cancel := context.WithCancel(ctx) + go func() { + <-b.ShutdownCh + cancel() + }() + + iamRepo, err := iam.NewRepository(rw, rw, kmsCache) + if err != nil { + return fmt.Errorf("error creating scopes repository: %w", err) + } + + // Create the scopes + if b.DevOrgId == "" { + b.DevOrgId, err = db.NewPublicId(scope.Org.Prefix()) + if err != nil { + return fmt.Errorf("error generating initial org id: %w", err) + } + } + opts := []iam.Option{ + iam.WithName("Generated org scope"), + iam.WithDescription("Provides an initial org scope in Boundary"), + iam.WithRandomReader(b.SecureRandomReader), + iam.WithPublicId(b.DevOrgId), + } + orgScope, err := iam.NewOrg(opts...) + if err != nil { + return fmt.Errorf("error creating new in memory org scope: %w", err) + } + _, err = iamRepo.CreateScope(cancelCtx, orgScope, b.DevUserId, opts...) + if err != nil { + return fmt.Errorf("error saving org scope to the db: %w", err) + } + b.InfoKeys = append(b.InfoKeys, "generated org scope id") + b.Info["generated org scope id"] = b.DevOrgId + + if b.DevProjectId == "" { + b.DevProjectId, err = db.NewPublicId(scope.Project.Prefix()) + if err != nil { + return fmt.Errorf("error generating initial project id: %w", err) + } + } + opts = []iam.Option{ + iam.WithName("Generated project scope"), + iam.WithDescription("Provides an initial project scope in Boundary"), + iam.WithRandomReader(b.SecureRandomReader), + iam.WithPublicId(b.DevProjectId), + } + projScope, err := iam.NewProject(b.DevOrgId, opts...) + if err != nil { + return fmt.Errorf("error creating new in memory project scope: %w", err) + } + _, err = iamRepo.CreateScope(cancelCtx, projScope, b.DevUserId, opts...) + if err != nil { + return fmt.Errorf("error saving project scope to the db: %w", err) + } + b.InfoKeys = append(b.InfoKeys, "generated project scope id") + b.Info["generated project scope id"] = b.DevProjectId + + return nil +} + +func (b *Server) CreateInitialHostResources(ctx context.Context) 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, kms.WithLogger(b.Logger.Named("kms"))) + 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) + } + + cancelCtx, cancel := context.WithCancel(ctx) + go func() { + <-b.ShutdownCh + cancel() + }() + + staticRepo, err := static.NewRepository(rw, rw, kmsCache) + if err != nil { + return fmt.Errorf("error creating static repository: %w", err) + } + + // Host Catalog + if b.DevHostCatalogId == "" { + b.DevHostCatalogId, err = db.NewPublicId(static.HostCatalogPrefix) + if err != nil { + return fmt.Errorf("error generating initial host catalog id: %w", err) + } + } + opts := []static.Option{ + static.WithName("Generated host catalog"), + static.WithDescription("Provides an initial host catalog in Boundary"), + static.WithPublicId(b.DevHostCatalogId), + } + hc, err := static.NewHostCatalog(b.DevProjectId, opts...) + if err != nil { + return fmt.Errorf("error creating in memory host catalog: %w", err) + } + if hc, err = staticRepo.CreateCatalog(cancelCtx, hc, opts...); err != nil { + return fmt.Errorf("error saving host catalog to the db: %w", err) + } + b.InfoKeys = append(b.InfoKeys, "generated host catalog id") + b.Info["generated host catalog id"] = b.DevHostCatalogId + + // Host + if b.DevHostId == "" { + b.DevHostId, err = db.NewPublicId(static.HostPrefix) + if err != nil { + return fmt.Errorf("error generating initial host id: %w", err) + } + } + if b.DevHostAddress == "" { + b.DevHostAddress = "localhost" + } + opts = []static.Option{ + static.WithName("Generated host"), + static.WithDescription("Provides an initial host in Boundary"), + static.WithAddress(b.DevHostAddress), + static.WithPublicId(b.DevHostId), + } + h, err := static.NewHost(hc.PublicId, opts...) + if err != nil { + return fmt.Errorf("error creating in memory host: %w", err) + } + if h, err = staticRepo.CreateHost(cancelCtx, b.DevProjectId, h, opts...); err != nil { + return fmt.Errorf("error saving host to the db: %w", err) + } + b.InfoKeys = append(b.InfoKeys, "generated host id") + b.Info["generated host id"] = b.DevHostId + + // Host Set + if b.DevHostSetId == "" { + b.DevHostSetId, err = db.NewPublicId(static.HostSetPrefix) + if err != nil { + return fmt.Errorf("error generating initial host set id: %w", err) + } + } + opts = []static.Option{ + static.WithName("Generated host set"), + static.WithDescription("Provides an initial host set in Boundary"), + static.WithPublicId(b.DevHostSetId), + } + hs, err := static.NewHostSet(hc.PublicId, opts...) + if err != nil { + return fmt.Errorf("error creating in memory host set: %w", err) + } + if hs, err = staticRepo.CreateSet(cancelCtx, b.DevProjectId, hs, opts...); err != nil { + return fmt.Errorf("error saving host set to the db: %w", err) + } + b.InfoKeys = append(b.InfoKeys, "generated host set id") + b.Info["generated host set id"] = b.DevHostSetId + + // Associate members + if _, err := staticRepo.AddSetMembers(cancelCtx, b.DevProjectId, b.DevHostSetId, hs.GetVersion(), []string{h.GetPublicId()}); err != nil { + return fmt.Errorf("error associating host set to host in the db: %w", err) + } + + return nil +} + +func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error) { + rw := db.New(b.Database) + + kmsRepo, err := kms.NewRepository(rw, rw) + if err != nil { + return nil, fmt.Errorf("error creating kms repository: %w", err) + } + kmsCache, err := kms.NewKms(kmsRepo, kms.WithLogger(b.Logger.Named("kms"))) + if err != nil { + return nil, fmt.Errorf("error creating kms cache: %w", err) + } + if err := kmsCache.AddExternalWrappers( + kms.WithRootWrapper(b.RootKms), + ); err != nil { + return nil, fmt.Errorf("error adding config keys to kms: %w", err) + } + + cancelCtx, cancel := context.WithCancel(ctx) + go func() { + <-b.ShutdownCh + cancel() + }() + + targetRepo, err := target.NewRepository(rw, rw, kmsCache) + if err != nil { + return nil, fmt.Errorf("error creating target repository: %w", err) + } + + // Host Catalog + if b.DevTargetId == "" { + b.DevTargetId, err = db.NewPublicId(target.TcpTargetPrefix) + if err != nil { + return nil, fmt.Errorf("error generating initial target id: %w", err) + } + } + if b.DevTargetDefaultPort == 0 { + b.DevTargetDefaultPort = 22 + } + opts := []target.Option{ + target.WithName("Generated target"), + target.WithDescription("Provides an initial target in Boundary"), + target.WithDefaultPort(uint32(b.DevTargetDefaultPort)), + target.WithHostSets([]string{b.DevHostSetId}), + target.WithPublicId(b.DevTargetId), + } + t, err := target.NewTcpTarget(b.DevProjectId, opts...) + if err != nil { + return nil, fmt.Errorf("error creating in memory target: %w", err) + } + tt, _, err := targetRepo.CreateTcpTarget(cancelCtx, t, opts...) + if err != nil { + return nil, fmt.Errorf("error saving target to the db: %w", err) + } + b.InfoKeys = append(b.InfoKeys, "generated target id") + b.Info["generated target id"] = b.DevTargetId + + return tt, nil +} diff --git a/internal/cmd/base/option.go b/internal/cmd/base/option.go index ab8f29c27..fe29936c3 100644 --- a/internal/cmd/base/option.go +++ b/internal/cmd/base/option.go @@ -14,9 +14,13 @@ type Option func(*Options) // Options - how Options are represented. type Options struct { - withNoTokenScope bool - withNoTokenValue bool - withSkipAuthMethodCreation bool + withNoTokenScope bool + withNoTokenValue bool + withSkipDatabaseDestruction bool + withSkipAuthMethodCreation bool + withSkipScopesCreation bool + withSkipHostResourcesCreation bool + withSkipTargetCreation bool } func getDefaultOptions() Options { @@ -32,6 +36,13 @@ func WithNoTokenScope() Option { } } +// WithSkipDatabaseDestruction tells the command not to destroy the database even on error. +func WithSkipDatabaseDestruction() Option { + return func(o *Options) { + o.withSkipDatabaseDestruction = true + } +} + // WithNoTokenValue tells the client not to set a token for the client from a // saved token's value, as this can cause confusing behavior at authentication // time. @@ -48,3 +59,27 @@ func WithSkipAuthMethodCreation() Option { o.withSkipAuthMethodCreation = true } } + +// WithSkipScopesCreation tells the command not to instantiate scopes on first +// run. +func WithSkipScopesCreation() Option { + return func(o *Options) { + o.withSkipScopesCreation = true + } +} + +// WithSkipHostResourcesCreation tells the command not to instantiate a host +// catalog and related resources on first run. +func WithSkipHostResourcesCreation() Option { + return func(o *Options) { + o.withSkipHostResourcesCreation = true + } +} + +// WithSkipTargetCreation tells the command not to instantiate a target on first +// run. +func WithSkipTargetCreation() Option { + return func(o *Options) { + o.withSkipTargetCreation = true + } +} diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index 058dd01da..8a34ceb88 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -16,10 +16,8 @@ import ( "github.com/armon/go-metrics" "github.com/hashicorp/boundary/globals" - "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/db" - "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/boundary/sdk/strutil" @@ -31,7 +29,6 @@ import ( "github.com/hashicorp/shared-secure-libs/configutil" "github.com/hashicorp/shared-secure-libs/gatedwriter" "github.com/hashicorp/shared-secure-libs/reloadutil" - "github.com/hashicorp/vault/sdk/helper/base62" "github.com/hashicorp/vault/sdk/helper/logging" "github.com/hashicorp/vault/sdk/helper/mlock" "github.com/hashicorp/vault/sdk/logical" @@ -68,15 +65,18 @@ type Server struct { Listeners []*ServerListener - DevAuthMethodId string - DevLoginName string - DevPassword string - DevOrgId string - DevProjectId string - DevHostCatalogId string - DevHostSetId string - DevHostId string - DevTargetId string + DevAuthMethodId string + DevLoginName string + DevPassword string + DevUserId string + DevOrgId string + DevProjectId string + DevHostCatalogId string + DevHostSetId string + DevHostId string + DevTargetId string + DevHostAddress string + DevTargetDefaultPort int DatabaseUrl string DevDatabaseCleanupFunc func() error @@ -443,8 +443,10 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error { // In case of an error, run the cleanup function. If we pass all errors, c should be set to a noop // function before returning from this method defer func() { - if err := c(); err != nil { - b.Logger.Error("error cleaning up docker container", "error", err) + if !opts.withSkipDatabaseDestruction { + if err := c(); err != nil { + b.Logger.Error("error cleaning up docker container", "error", err) + } } }() @@ -483,6 +485,39 @@ func (b *Server) CreateDevDatabase(dialect string, opt ...Option) error { return err } + if opts.withSkipScopesCreation { + // 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.CreateInitialScopes(context.Background()); err != nil { + return err + } + + if opts.withSkipHostResourcesCreation { + // 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.CreateInitialHostResources(context.Background()); err != nil { + return err + } + + if opts.withSkipTargetCreation { + // 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.CreateInitialTarget(context.Background()); 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 } @@ -520,105 +555,6 @@ func (b *Server) CreateGlobalKmsKeys(ctx context.Context) error { return nil } -func (b *Server) CreateInitialAuthMethod(ctx context.Context) 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 - pwRepo, err := password.NewRepository(rw, rw, kmsCache) - if err != nil { - return fmt.Errorf("error creating password repo: %w", err) - } - authMethod, err := password.NewAuthMethod(scope.Global.String()) - if err != nil { - return fmt.Errorf("error creating new in memory auth method: %w", err) - } - if b.DevAuthMethodId == "" { - b.DevAuthMethodId, err = db.NewPublicId(password.AuthMethodPrefix) - if err != nil { - return fmt.Errorf("error generating dev auth method id: %w", err) - } - } - - cancelCtx, cancel := context.WithCancel(ctx) - go func() { - <-b.ShutdownCh - cancel() - }() - - _, err = pwRepo.CreateAuthMethod(cancelCtx, 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, "generated auth method id") - b.Info["generated auth method id"] = b.DevAuthMethodId - - // Create the dev user - if b.DevLoginName == "" { - b.DevLoginName, err = base62.Random(10) - if err != nil { - return fmt.Errorf("unable to generate login name: %w", err) - } - b.DevLoginName = strings.ToLower(b.DevLoginName) - } - if b.DevPassword == "" { - b.DevPassword, err = base62.Random(20) - if err != nil { - return fmt.Errorf("unable to generate password: %w", err) - } - } - b.InfoKeys = append(b.InfoKeys, "generated password") - b.Info["generated password"] = b.DevPassword - - 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(cancelCtx, 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, "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)) - if err != nil { - return fmt.Errorf("unable to create repo for org id: %w", err) - } - pr, err := iam.NewRole(scope.Global.String()) - if err != nil { - return fmt.Errorf("error creating in memory role for generated grants: %w", err) - } - pr.Name = "Generated Global Scope Admin Role" - pr.Description = `Provides admin grants to all authenticated users within the "global" scope` - defPermsRole, err := iamRepo.CreateRole(cancelCtx, pr) - if err != nil { - return fmt.Errorf("error creating role for default generated grants: %w", err) - } - if _, err := iamRepo.AddRoleGrants(cancelCtx, defPermsRole.PublicId, defPermsRole.Version, []string{"id=*;actions=*"}); err != nil { - return fmt.Errorf("error creating grant for default generated grants: %w", err) - } - if _, err := iamRepo.AddPrincipalRoles(cancelCtx, defPermsRole.PublicId, defPermsRole.Version+1, []string{"u_auth"}, nil); err != nil { - return fmt.Errorf("error adding principal to role for default generated grants: %w", err) - } - - return nil -} - func (b *Server) DestroyDevDatabase() error { if b.Database != nil { b.Database.Close() diff --git a/internal/cmd/commands/database/funcs.go b/internal/cmd/commands/database/funcs.go index fee49d4b5..a60dd1a9c 100644 --- a/internal/cmd/commands/database/funcs.go +++ b/internal/cmd/commands/database/funcs.go @@ -1,6 +1,8 @@ package database import ( + "fmt" + "github.com/hashicorp/boundary/internal/cmd/base" ) @@ -9,6 +11,7 @@ type AuthMethodInfo struct { LoginName string `json:"login_name"` Password string `json:"password"` ScopeId string `json:"scope_id"` + UserId string `json:"user_id"` } func generateInitialAuthMethodTableOutput(in *AuthMethodInfo) string { @@ -17,6 +20,7 @@ func generateInitialAuthMethodTableOutput(in *AuthMethodInfo) string { "Auth Method ID": in.AuthMethodId, "Login Name": in.LoginName, "Password": in.Password, + "User ID": in.UserId, } maxLength := 0 @@ -26,12 +30,106 @@ func generateInitialAuthMethodTableOutput(in *AuthMethodInfo) string { } } - ret := []string{"", "Initial auth method information:"} - - ret = append(ret, - // We do +2 because there is another +2 offset for host sets below + ret := []string{ + "", + "Initial auth method information:", base.WrapMap(2, maxLength+2, nonAttributeMap), - ) + } + + return base.WrapForHelpText(ret) +} + +type ScopeInfo struct { + ScopeId string `json:"scope_id"` + Type string `json:"type"` +} + +func generateInitialScopeTableOutput(in *ScopeInfo) string { + nonAttributeMap := map[string]interface{}{ + "Scope ID": in.ScopeId, + "Type": in.Type, + } + + maxLength := 0 + for k := range nonAttributeMap { + if len(k) > maxLength { + maxLength = len(k) + } + } + + ret := []string{ + "", + fmt.Sprintf("Initial %s scope information:", in.Type), + base.WrapMap(2, maxLength+2, nonAttributeMap), + } + + return base.WrapForHelpText(ret) +} + +type HostInfo struct { + HostCatalogId string `json:"host_catalog_id"` + HostSetId string `json:"host_set_id"` + HostId string `json:"host_id"` + Type string `json:"type"` + ScopeId string `json:"scope_id"` +} + +func generateInitialHostResourcesTableOutput(in *HostInfo) string { + nonAttributeMap := map[string]interface{}{ + "Host Catalog ID": in.HostCatalogId, + "Host Set ID": in.HostSetId, + "Host ID": in.HostId, + "Type": in.Type, + "Scope ID": in.ScopeId, + } + + maxLength := 0 + for k := range nonAttributeMap { + if len(k) > maxLength { + maxLength = len(k) + } + } + + ret := []string{ + "", + "Initial host resources information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + } + + return base.WrapForHelpText(ret) +} + +type TargetInfo struct { + TargetId string `json:"target_id"` + DefaultPort uint32 `json:"default_port"` + SessionMaxSeconds uint32 `json:"session_max_seconds"` + SessionConnectionLimit int32 `json:"session_connection_limit"` + Type string `json:"type"` + ScopeId string `json:"scope_id"` +} + +func generateInitialTargetTableOutput(in *TargetInfo) string { + nonAttributeMap := map[string]interface{}{ + "Target ID": in.TargetId, + "Default Port": in.DefaultPort, + "Session Max Seconds": in.SessionMaxSeconds, + "Session Connection Limit": in.SessionConnectionLimit, + "Type": in.Type, + "Scope ID": in.ScopeId, + } + + maxLength := 0 + for k := range nonAttributeMap { + if len(k) > maxLength { + maxLength = len(k) + } + } + + ret := []string{ + "", + "Initial target information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + } return base.WrapForHelpText(ret) } diff --git a/internal/cmd/commands/database/init.go b/internal/cmd/commands/database/init.go index 2afdcb2a0..0e48ba04f 100644 --- a/internal/cmd/commands/database/init.go +++ b/internal/cmd/commands/database/init.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/db" - "github.com/hashicorp/boundary/internal/servers/controller" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/boundary/sdk/wrapper" wrapping "github.com/hashicorp/go-kms-wrapping" @@ -24,22 +23,23 @@ type InitCommand struct { *base.Command srv *base.Server - SighupCh chan struct{} - childSighupCh []chan struct{} - ReloadedCh chan struct{} - SigUSR2Ch chan struct{} + SighupCh chan struct{} + ReloadedCh chan struct{} + SigUSR2Ch chan struct{} - Config *config.Config - controller *controller.Controller + Config *config.Config configWrapper wrapping.Wrapper - flagConfig string - flagConfigKms string - flagLogLevel string - flagLogFormat string - flagMigrationUrl string - flagSkipAuthMethodCreation bool + flagConfig string + flagConfigKms string + flagLogLevel string + flagLogFormat string + flagMigrationUrl string + flagSkipAuthMethodCreation bool + flagSkipScopesCreation bool + flagSkipHostResourcesCreation bool + flagSkipTargetCreation bool } func (c *InitCommand) Synopsis() string { @@ -47,17 +47,27 @@ func (c *InitCommand) Synopsis() string { } func (c *InitCommand) Help() string { - helpText := ` -Usage: boundary database init [options] - - Initialize Boundary's database: - - $ boundary database init -config=/etc/boundary/controller.hcl - - For a full list of examples, please see the documentation. - -` + c.Flags().Help() - return strings.TrimSpace(helpText) + return base.WrapForHelpText([]string{ + "Usage: boundary database init [options]", + "", + " Initialize Boundary's database:", + "", + " $ boundary database init -config=/etc/boundary/controller.hcl", + "", + " Unless told not to via flags, some initial resources will be created, in the following order and in the indicated scopes:", + "", + " Password-Type Auth Method (global)", + " Org Scope (global)", + " Project Scope (org)", + " Static-Type Host Catalog (project)", + " Static-Type Host Set", + " Static-Type Host", + " Target (project)", + "", + " If flags are used to skip any of these resources, any resources that would be created afterwards are also skipped.", + "", + " For a full list of examples, please see the documentation.", + }) + c.Flags().Help() } func (c *InitCommand) Flags() *base.FlagSets { @@ -111,6 +121,24 @@ func (c *InitCommand) Flags() *base.FlagSets { Usage: "If not set, an auth method will not be created as part of initialization. If set, the recovery KMS will be needed to perform any actions.", }) + f.BoolVar(&base.BoolVar{ + Name: "skip-scopes-creation", + Target: &c.flagSkipScopesCreation, + Usage: "If not set, scopes will not be created as part of initialization.", + }) + + f.BoolVar(&base.BoolVar{ + Name: "skip-host-resources-creation", + Target: &c.flagSkipHostResourcesCreation, + Usage: "If not set, host resources (host catalog, host set, host) will not be created as part of initialization.", + }) + + f.BoolVar(&base.BoolVar{ + Name: "skip-target-creation", + Target: &c.flagSkipTargetCreation, + Usage: "If not set, a target will not be created as part of initialization.", + }) + f.StringVar(&base.StringVar{ Name: "migration-url", Target: &c.flagMigrationUrl, @@ -129,7 +157,7 @@ func (c *InitCommand) AutocompleteFlags() complete.Flags { return c.Flags().Completions() } -func (c *InitCommand) Run(args []string) int { +func (c *InitCommand) Run(args []string) (retCode int) { if result := c.ParseFlagsAndConfig(args); result > 0 { return result } @@ -262,6 +290,20 @@ func (c *InitCommand) Run(args []string) int { return 0 } + var jsonMap map[string]interface{} + if base.Format(c.UI) == "json" { + jsonMap = make(map[string]interface{}) + defer func() { + b, err := base.JsonFormatter{}.Format(jsonMap) + if err != nil { + c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) + retCode = 1 + return + } + c.UI.Output(string(b)) + }() + } + // Use an easy name, at least c.srv.DevLoginName = "admin" if err := c.srv.CreateInitialAuthMethod(c.Context); err != nil { @@ -274,17 +316,92 @@ func (c *InitCommand) Run(args []string) int { LoginName: c.srv.DevLoginName, Password: c.srv.DevPassword, ScopeId: scope.Global.String(), + UserId: c.srv.DevUserId, } switch base.Format(c.UI) { case "table": c.UI.Output(generateInitialAuthMethodTableOutput(authMethodInfo)) case "json": - b, err := base.JsonFormatter{}.Format(authMethodInfo) - if err != nil { - c.UI.Error(fmt.Errorf("Error formatting as JSON: %w", err).Error()) - return 1 - } - c.UI.Output(string(b)) + jsonMap["auth_method"] = authMethodInfo + } + + if c.flagSkipScopesCreation { + return 0 + } + + if err := c.srv.CreateInitialScopes(c.Context); err != nil { + c.UI.Error(fmt.Errorf("Error creating initial scopes: %w", err).Error()) + return 1 + } + + orgScopeInfo := &ScopeInfo{ + ScopeId: c.srv.DevOrgId, + Type: scope.Org.String(), + } + switch base.Format(c.UI) { + case "table": + c.UI.Output(generateInitialScopeTableOutput(orgScopeInfo)) + case "json": + jsonMap["org_scope"] = orgScopeInfo + } + + projScopeInfo := &ScopeInfo{ + ScopeId: c.srv.DevProjectId, + Type: scope.Project.String(), + } + switch base.Format(c.UI) { + case "table": + c.UI.Output(generateInitialScopeTableOutput(projScopeInfo)) + case "json": + jsonMap["proj_scope"] = projScopeInfo + } + + if c.flagSkipHostResourcesCreation { + return 0 + } + + if err := c.srv.CreateInitialHostResources(c.Context); err != nil { + c.UI.Error(fmt.Errorf("Error creating initial host resources: %w", err).Error()) + return 1 + } + + hostInfo := &HostInfo{ + HostCatalogId: c.srv.DevHostCatalogId, + HostSetId: c.srv.DevHostSetId, + HostId: c.srv.DevHostId, + Type: "static", + ScopeId: c.srv.DevProjectId, + } + switch base.Format(c.UI) { + case "table": + c.UI.Output(generateInitialHostResourcesTableOutput(hostInfo)) + case "json": + jsonMap["host_resources"] = hostInfo + } + + if c.flagSkipTargetCreation { + return 0 + } + + t, err := c.srv.CreateInitialTarget(c.Context) + if err != nil { + c.UI.Error(fmt.Errorf("Error creating initial target: %w", err).Error()) + return 1 + } + + targetInfo := &TargetInfo{ + TargetId: c.srv.DevTargetId, + DefaultPort: t.GetDefaultPort(), + SessionMaxSeconds: t.GetSessionMaxSeconds(), + SessionConnectionLimit: t.GetSessionConnectionLimit(), + Type: "tcp", + ScopeId: c.srv.DevProjectId, + } + switch base.Format(c.UI) { + case "table": + c.UI.Output(generateInitialTargetTableOutput(targetInfo)) + case "json": + jsonMap["target"] = targetInfo } return 0 diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 85bb964c8..3b82bc4db 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -2,6 +2,7 @@ package dev import ( "fmt" + "net" "runtime" "strings" @@ -9,9 +10,11 @@ import ( "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/servers/controller" "github.com/hashicorp/boundary/internal/servers/controller/handlers" "github.com/hashicorp/boundary/internal/servers/worker" + "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/types/scope" "github.com/mitchellh/cli" "github.com/posener/complete" @@ -38,6 +41,8 @@ type Command struct { flagLoginName string flagPassword string flagIdSuffix string + flagHostAddress string + flagTargetDefaultPort int flagControllerAPIListenAddr string flagControllerClusterListenAddr string flagWorkerProxyListenAddr string @@ -89,10 +94,11 @@ func (c *Command) Flags() *base.FlagSets { }) f.StringVar(&base.StringVar{ - Name: "id-suffix", - Target: &c.flagIdSuffix, - EnvVar: "BOUNDARY_DEV_ID_SUFFIX", - Usage: `If set, auto-created resources will use this value for their identifier (along with their resource-specific prefix). Must be 10 alphanumeric characters. As an example, if this is set to "1234567890", the generated password auth method ID will be "ampw_1234567890", the generated TCP target ID will be "ttcp_1234567890", and so on.`, + Name: "id-suffix", + Target: &c.flagIdSuffix, + Default: "1234567890", + EnvVar: "BOUNDARY_DEV_ID_SUFFIX", + Usage: `If set, auto-created resources will use this value for their identifier (along with their resource-specific prefix). Must be 10 alphanumeric characters. As an example, if this is set to "1234567890", the generated password auth method ID will be "ampw_1234567890", the generated TCP target ID will be "ttcp_1234567890", and so on.`, }) f.StringVar(&base.StringVar{ @@ -116,6 +122,22 @@ func (c *Command) Flags() *base.FlagSets { Usage: "Address to bind to for controller \"api\" purpose.", }) + f.StringVar(&base.StringVar{ + Name: "host-address", + Default: "localhost", + Target: &c.flagHostAddress, + EnvVar: "BOUNDARY_DEV_HOST_ADDRESS", + Usage: "Address to use for the default host that is created. Must be a bare host or IP address, no port.", + }) + + f.IntVar(&base.IntVar{ + Name: "target-default-port", + Default: 22, + Target: &c.flagTargetDefaultPort, + EnvVar: "BOUNDARY_DEV_TARGET_DEFAULT_PORT", + Usage: "Default port to use for the default target that is created.", + }) + f.StringVar(&base.StringVar{ Name: "cluster-listen-address", Target: &c.flagControllerClusterListenAddr, @@ -203,11 +225,13 @@ func (c *Command) Run(args []string) int { return 1 } c.DevAuthMethodId = fmt.Sprintf("%s_%s", password.AuthMethodPrefix, c.flagIdSuffix) + c.DevUserId = fmt.Sprintf("%s_%s", iam.UserPrefix, c.flagIdSuffix) c.DevOrgId = fmt.Sprintf("%s_%s", scope.Org.Prefix(), c.flagIdSuffix) c.DevProjectId = fmt.Sprintf("%s_%s", scope.Project.Prefix(), c.flagIdSuffix) c.DevHostCatalogId = fmt.Sprintf("%s_%s", static.HostCatalogPrefix, c.flagIdSuffix) c.DevHostSetId = fmt.Sprintf("%s_%s", static.HostSetPrefix, c.flagIdSuffix) c.DevHostId = fmt.Sprintf("%s_%s", static.HostPrefix, c.flagIdSuffix) + c.DevTargetId = fmt.Sprintf("%s_%s", target.TcpTargetPrefix, c.flagIdSuffix) } if c.flagLoginName != "" { c.DevLoginName = c.flagLoginName @@ -215,6 +239,20 @@ func (c *Command) Run(args []string) int { if c.flagPassword != "" { c.DevPassword = c.flagPassword } + c.DevTargetDefaultPort = c.flagTargetDefaultPort + host, port, err := net.SplitHostPort(c.flagHostAddress) + if err != nil { + if !strings.Contains(err.Error(), "missing port") { + c.UI.Error(fmt.Errorf("Invalid host address specified: %w", err).Error()) + return 1 + } + host = c.flagHostAddress + } + if port != "" { + c.UI.Error(`Port must not be specified as part of the dev host address`) + return 1 + } + c.DevHostAddress = host c.Config.PassthroughDirectory = c.flagPassthroughDirectory @@ -301,6 +339,9 @@ func (c *Command) Run(args []string) int { }() var opts []base.Option + if c.flagDisableDatabaseDestruction { + opts = append(opts, base.WithSkipDatabaseDestruction()) + } if err := c.CreateDevDatabase("postgres", opts...); err != nil { c.UI.Error(fmt.Errorf("Error creating dev database container: %w", err).Error()) return 1 @@ -361,45 +402,6 @@ func (c *Command) Run(args []string) int { return 1 } } - /* - shutdownWg := &sync.WaitGroup{} - shutdownWg.Add(2) - controllerSighupCh := make(chan struct{}) - c.childSighupCh = append(c.childSighupCh, controllerSighupCh) - - devController := &controllercmd.Command{ - Server: c.Server, - ExtShutdownCh: childShutdownCh, - SighupCh: controllerSighupCh, - Config: c.Config, - } - if err := devController.Start(); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - workerSighupCh := make(chan struct{}) - c.childSighupCh = append(c.childSighupCh, workerSighupCh) - devWorker := &workercmd.Command{ - Server: c.Server, - ExtShutdownCh: childShutdownCh, - SighupCh: workerSighupCh, - Config: c.Config, - } - if err := devWorker.Start(); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - go func() { - defer shutdownWg.Done() - devController.WaitForInterrupt() - }() - go func() { - defer shutdownWg.Done() - devWorker.WaitForInterrupt() - }() - */ // Wait for shutdown shutdownTriggered := false @@ -409,10 +411,6 @@ func (c *Command) Run(args []string) int { case <-c.ShutdownCh: c.UI.Output("==> Boundary dev environment shutdown triggered") - /* - childShutdownCh <- struct{}{} - childShutdownCh <- struct{}{} - */ if err := c.worker.Shutdown(false); err != nil { c.UI.Error(fmt.Errorf("Error shutting down worker: %w", err).Error()) } @@ -423,14 +421,6 @@ func (c *Command) Run(args []string) int { shutdownTriggered = true - /* - case <-c.SighupCh: - c.UI.Output("==> Boundary dev environment reload triggered") - for _, v := range c.childSighupCh { - v <- struct{}{} - } - */ - case <-c.SigUSR2Ch: buf := make([]byte, 32*1024*1024) n := runtime.Stack(buf[:], true) @@ -438,7 +428,5 @@ func (c *Command) Run(args []string) int { } } - //shutdownWg.Wait() - return 0 } diff --git a/internal/host/static/options.go b/internal/host/static/options.go index 8497f70d1..46e85535a 100644 --- a/internal/host/static/options.go +++ b/internal/host/static/options.go @@ -18,6 +18,7 @@ type options struct { withDescription string withLimit int withAddress string + withPublicId string } func getDefaultOptions() options { @@ -57,3 +58,10 @@ func WithAddress(address string) Option { o.withAddress = address } } + +// WithPublicId provides an optional public id +func WithPublicId(id string) Option { + return func(o *options) { + o.withPublicId = id + } +} diff --git a/internal/host/static/options_test.go b/internal/host/static/options_test.go index d02eec0bb..dccfd5adb 100644 --- a/internal/host/static/options_test.go +++ b/internal/host/static/options_test.go @@ -32,4 +32,10 @@ func Test_GetOpts(t *testing.T) { testOpts.withAddress = "test" assert.Equal(t, opts, testOpts) }) + t.Run("WithPublicId", func(t *testing.T) { + opts := getOpts(WithPublicId("test")) + testOpts := getDefaultOptions() + testOpts.withPublicId = "test" + assert.Equal(t, opts, testOpts) + }) } diff --git a/internal/host/static/repository_host.go b/internal/host/static/repository_host.go index 8ac4228b0..ade80c02b 100644 --- a/internal/host/static/repository_host.go +++ b/internal/host/static/repository_host.go @@ -43,11 +43,20 @@ func (r *Repository) CreateHost(ctx context.Context, scopeId string, h *Host, op } h = h.clone() - id, err := newHostId() - if err != nil { - return nil, fmt.Errorf("create: static host: %w", err) + opts := getOpts(opt...) + + if opts.withPublicId != "" { + if !strings.HasPrefix(opts.withPublicId, HostPrefix+"_") { + return nil, fmt.Errorf("create: static host: passed-in public ID %q has wrong prefix, should be %q: %w", opts.withPublicId, HostPrefix, db.ErrInvalidPublicId) + } + h.PublicId = opts.withPublicId + } else { + id, err := newHostId() + if err != nil { + return nil, fmt.Errorf("create: static host: %w", err) + } + h.PublicId = id } - h.PublicId = id oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) if err != nil { diff --git a/internal/host/static/repository_host_catalog.go b/internal/host/static/repository_host_catalog.go index 469638ac3..74efb4deb 100644 --- a/internal/host/static/repository_host_catalog.go +++ b/internal/host/static/repository_host_catalog.go @@ -35,11 +35,20 @@ func (r *Repository) CreateCatalog(ctx context.Context, c *HostCatalog, opt ...O } c = c.clone() - id, err := newHostCatalogId() - if err != nil { - return nil, fmt.Errorf("create: static host catalog: %w", err) + opts := getOpts(opt...) + + if opts.withPublicId != "" { + if !strings.HasPrefix(opts.withPublicId, HostCatalogPrefix+"_") { + return nil, fmt.Errorf("create: static host catalog: passed-in public ID %q has wrong prefix, should be %q: %w", opts.withPublicId, HostCatalogPrefix, db.ErrInvalidPublicId) + } + c.PublicId = opts.withPublicId + } else { + id, err := newHostCatalogId() + if err != nil { + return nil, fmt.Errorf("create: static host catalog: %w", err) + } + c.PublicId = id } - c.PublicId = id oplogWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeOplog) if err != nil { diff --git a/internal/host/static/repository_host_set.go b/internal/host/static/repository_host_set.go index a0201308a..29e43090c 100644 --- a/internal/host/static/repository_host_set.go +++ b/internal/host/static/repository_host_set.go @@ -37,11 +37,20 @@ func (r *Repository) CreateSet(ctx context.Context, scopeId string, s *HostSet, } s = s.clone() - id, err := newHostSetId() - if err != nil { - return nil, fmt.Errorf("create: static host set: %w", err) + opts := getOpts(opt...) + + if opts.withPublicId != "" { + if !strings.HasPrefix(opts.withPublicId, HostSetPrefix+"_") { + return nil, fmt.Errorf("create: static host set: passed-in public ID %q has wrong prefix, should be %q: %w", opts.withPublicId, HostSetPrefix, db.ErrInvalidPublicId) + } + s.PublicId = opts.withPublicId + } else { + id, err := newHostSetId() + if err != nil { + return nil, fmt.Errorf("create: static host set: %w", err) + } + s.PublicId = id } - s.PublicId = id oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) if err != nil { diff --git a/internal/iam/repository_scope.go b/internal/iam/repository_scope.go index cb29bad31..1f3a05e96 100644 --- a/internal/iam/repository_scope.go +++ b/internal/iam/repository_scope.go @@ -110,7 +110,7 @@ func (r *Repository) CreateScope(ctx context.Context, s *Scope, userId string, o } role.PublicId = rolePublicId role.Name = "on-scope-creation" - role.Description = fmt.Sprintf("Role created for administration of scope %s at its creation time", scopePublicId) + role.Description = fmt.Sprintf("Role created for administration of scope %s by user %s at its creation time", scopePublicId, userId) roleRaw = role roleMetadata = oplog.Metadata{ "resource-public-id": []string{rolePublicId}, diff --git a/internal/iam/repository_user.go b/internal/iam/repository_user.go index 1b22bdafc..94c479ea3 100644 --- a/internal/iam/repository_user.go +++ b/internal/iam/repository_user.go @@ -21,18 +21,29 @@ func (r *Repository) CreateUser(ctx context.Context, user *User, opt ...Option) if user.PublicId != "" { return nil, fmt.Errorf("create user: public id is not empty %w", db.ErrInvalidParameter) } - id, err := newUserId() - if err != nil { - return nil, fmt.Errorf("create user: %w", err) + u := user.Clone().(*User) + + opts := getOpts(opt...) + + if opts.withPublicId != "" { + if !strings.HasPrefix(opts.withPublicId, UserPrefix+"_") { + return nil, fmt.Errorf("create user: passed-in public ID %q has wrong prefix, should be %q: %w", opts.withPublicId, UserPrefix, db.ErrInvalidPublicId) + } + u.PublicId = opts.withPublicId + } else { + id, err := newUserId() + if err != nil { + return nil, fmt.Errorf("create user: %w", err) + } + u.PublicId = id } - u := user.Clone() - u.(*User).PublicId = id - resource, err := r.create(ctx, u.(*User)) + + resource, err := r.create(ctx, u) if err != nil { if db.IsUniqueError(err) { return nil, fmt.Errorf("create user: user %s already exists in org %s: %w", user.Name, user.ScopeId, err) } - return nil, fmt.Errorf("create user: %w for %s", err, u.(*User).PublicId) + return nil, fmt.Errorf("create user: %w for %s", err, u.PublicId) } return resource.(*User), err } diff --git a/internal/servers/controller/handlers/scopes/scope_service_test.go b/internal/servers/controller/handlers/scopes/scope_service_test.go index 37ce989f1..a946a8d7d 100644 --- a/internal/servers/controller/handlers/scopes/scope_service_test.go +++ b/internal/servers/controller/handlers/scopes/scope_service_test.go @@ -612,7 +612,7 @@ func TestCreate(t *testing.T) { require.Len(roles, 1) role := roles[0] assert.Equal("on-scope-creation", role.GetName()) - assert.Equal(fmt.Sprintf("Role created for administration of scope %s at its creation time", got.GetItem().GetId()), role.GetDescription()) + assert.Equal(fmt.Sprintf("Role created for administration of scope %s by user %s at its creation time", got.GetItem().GetId(), userId), role.GetDescription()) } // Clear all values which are hard to compare against. diff --git a/internal/servers/controller/testing.go b/internal/servers/controller/testing.go index 36714183a..3d8e82167 100644 --- a/internal/servers/controller/testing.go +++ b/internal/servers/controller/testing.go @@ -239,6 +239,18 @@ type TestControllerOpts struct { // method automatically. DisableAuthMethodCreation bool + // DisableScopesCreation can be set true to disable creating scopes + // automatically. + DisableScopesCreation bool + + // DisableHostResourcesCreation can be set true to disable creating a host + // catalog and related resources automatically. + DisableHostResourcesCreation bool + + // DisableTargetCreation can be set true to disable creating a target + // automatically. + DisableTargetCreation bool + // DisableDatabaseCreation can be set true to disable creating a dev // database DisableDatabaseCreation bool @@ -382,10 +394,25 @@ func NewTestController(t *testing.T, opts *TestControllerOpts) *TestController { if err := tc.b.CreateGlobalKmsKeys(ctx); err != nil { t.Fatal(err) } - } - if !opts.DisableAuthMethodCreation { - if err := tc.b.CreateInitialAuthMethod(ctx); err != nil { - t.Fatal(err) + if !opts.DisableAuthMethodCreation { + if err := tc.b.CreateInitialAuthMethod(ctx); err != nil { + t.Fatal(err) + } + if !opts.DisableScopesCreation { + if err := tc.b.CreateInitialScopes(ctx); err != nil { + t.Fatal(err) + } + if !opts.DisableHostResourcesCreation { + if err := tc.b.CreateInitialHostResources(ctx); err != nil { + t.Fatal(err) + } + if !opts.DisableTargetCreation { + if _, err := tc.b.CreateInitialTarget(ctx); err != nil { + t.Fatal(err) + } + } + } + } } } } else if !opts.DisableDatabaseCreation { diff --git a/internal/target/options.go b/internal/target/options.go index 01f9716ca..12884fc80 100644 --- a/internal/target/options.go +++ b/internal/target/options.go @@ -26,6 +26,7 @@ type options struct { withHostSets []string withSessionMaxSeconds uint32 withSessionConnectionLimit int32 + withPublicId string } func getDefaultOptions() options { @@ -40,6 +41,7 @@ func getDefaultOptions() options { withHostSets: nil, withSessionMaxSeconds: uint32((8 * time.Hour).Seconds()), withSessionConnectionLimit: 1, + withPublicId: "", } } @@ -112,3 +114,10 @@ func WithSessionConnectionLimit(limit int32) Option { o.withSessionConnectionLimit = limit } } + +// WithPublicId provides an optional public id +func WithPublicId(id string) Option { + return func(o *options) { + o.withPublicId = id + } +} diff --git a/internal/target/options_test.go b/internal/target/options_test.go index 859c6be9e..78e5bf956 100644 --- a/internal/target/options_test.go +++ b/internal/target/options_test.go @@ -68,6 +68,13 @@ func Test_GetOpts(t *testing.T) { testOpts.withScopeId = "testId" assert.Equal(opts, testOpts) }) + t.Run("WithPublicId", func(t *testing.T) { + assert := assert.New(t) + opts := getOpts(WithPublicId("testId")) + testOpts := getDefaultOptions() + testOpts.withPublicId = "testId" + assert.Equal(opts, testOpts) + }) t.Run("WithTargetType", func(t *testing.T) { assert := assert.New(t) opts := getOpts(WithTargetType(TcpTargetType)) diff --git a/internal/target/repository_tcp_target.go b/internal/target/repository_tcp_target.go index 08dc7a32a..bd9e7a165 100644 --- a/internal/target/repository_tcp_target.go +++ b/internal/target/repository_tcp_target.go @@ -31,17 +31,27 @@ func (r *Repository) CreateTcpTarget(ctx context.Context, target *TcpTarget, opt if target.PublicId != "" { return nil, nil, fmt.Errorf("create tcp target: public id not empty: %w", db.ErrInvalidParameter) } - id, err := newTcpTargetId() - if err != nil { - return nil, nil, fmt.Errorf("create tcp target: %w", err) + + t := target.Clone().(*TcpTarget) + + if opts.withPublicId != "" { + if !strings.HasPrefix(opts.withPublicId, TcpTargetPrefix+"_") { + return nil, nil, fmt.Errorf("create tcp target: passed-in public ID %q has wrong prefix, should be %q: %w", opts.withPublicId, TcpTargetPrefix, db.ErrInvalidPublicId) + } + t.PublicId = opts.withPublicId + } else { + + id, err := newTcpTargetId() + if err != nil { + return nil, nil, fmt.Errorf("create tcp target: %w", err) + } + t.PublicId = id } oplogWrapper, err := r.kms.GetWrapper(ctx, target.ScopeId, kms.KeyPurposeOplog) if err != nil { return nil, nil, fmt.Errorf("create tcp target: unable to get oplog wrapper: %w", err) } - t := target.Clone().(*TcpTarget) - t.PublicId = id newHostSets := make([]interface{}, 0, len(opts.withHostSets)) for _, hsId := range opts.withHostSets {