diff --git a/internal/auth/auth.go b/internal/auth/auth.go index cf9e7930d..52ac0ddbd 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -449,7 +449,7 @@ func (v *verifier) decryptToken() { // verified is before the current time, with a minute of fudging), and // it's before now, it's expired and might be a replay. if info.CreationTime.Add(globals.RecoveryTokenValidityPeriod).Before(time.Now()) { - v.logger.Warn("WARNING: decrypt recovery token: recovery token has expired (possible replay attack)") + v.logger.Warn("decrypt recovery token: recovery token has expired (possible replay attack)") v.requestInfo.TokenFormat = AuthTokenTypeUnknown return } @@ -460,10 +460,10 @@ func (v *verifier) decryptToken() { return } if err := repo.AddRecoveryNonce(v.ctx, info.Nonce); err != nil { - v.logger.Warn("WARNING: decrypt recovery token: error adding nonce to database (possible replay attack)", "error", err) + v.logger.Warn("decrypt recovery token: error adding nonce to database (possible replay attack)", "error", err) v.requestInfo.TokenFormat = AuthTokenTypeUnknown return } - v.logger.Info("NOTE: recovery KMS was used to authorize a call", "url", v.requestInfo.Path, "method", v.requestInfo.Method) + v.logger.Warn("recovery KMS was used to authorize a call", "url", v.requestInfo.Path, "method", v.requestInfo.Method) } } diff --git a/internal/cmd/base/base.go b/internal/cmd/base/base.go index 1abdd1a10..a4a27c754 100644 --- a/internal/cmd/base/base.go +++ b/internal/cmd/base/base.go @@ -73,9 +73,6 @@ type Command struct { FlagDescription string FlagVersion int - FlagDevPassthroughDirectory string - FlagDevRecoveryKey string - client *api.Client } diff --git a/internal/cmd/base/dev_flags.go b/internal/cmd/base/dev_flags.go deleted file mode 100644 index 79e1aeac7..000000000 --- a/internal/cmd/base/dev_flags.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build dev - -package base - -func init() { - DevOnlyControllerFlags = addDevOnlyControllerFlags -} - -func addDevOnlyControllerFlags(c *Command, f *FlagSet) { - f.StringVar(&StringVar{ - Name: "dev-passthrough-directory", - Target: &c.FlagDevPassthroughDirectory, - EnvVar: "BOUNDARY_DEV_PASSTHROUGH_DIRECTORY", - Usage: "Enables a passthrough directory in the webserver at /", - }) - - f.StringVar(&StringVar{ - Name: "dev-recovery-key", - Target: &c.FlagDevRecoveryKey, - EnvVar: "BOUNDARY_DEV_RECOVERY_KEY", - Usage: "Specifies the base64'd 256-bit AES key to use for recovery operations", - }) -} diff --git a/internal/cmd/base/listener.go b/internal/cmd/base/listener.go index 524b9be5d..3b6b4957b 100644 --- a/internal/cmd/base/listener.go +++ b/internal/cmd/base/listener.go @@ -100,6 +100,15 @@ func tcpListenerFactory(l *configutil.Listener, logger hclog.Logger, ui cli.Ui) return nil, nil, nil, errors.New("could not determine port") } + switch purpose { + case "cluster": + l.TLSDisable = true + case "proxy": + // TODO: Eventually we'll support bringing your own cert, and we'd only + // want to disable if you aren't actually bringing your own + l.TLSDisable = true + } + bindProto := "tcp" // If they've passed 0.0.0.0, we only want to bind on IPv4 diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index b2ce25af9..058dd01da 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -68,9 +68,15 @@ type Server struct { Listeners []*ServerListener - DevAuthMethodId string - DevLoginName string - DevPassword string + DevAuthMethodId string + DevLoginName string + DevPassword string + DevOrgId string + DevProjectId string + DevHostCatalogId string + DevHostSetId string + DevHostId string + DevTargetId string DatabaseUrl string DevDatabaseCleanupFunc func() error @@ -198,7 +204,7 @@ func (b *Server) SetupMetrics(ui cli.Ui, telemetry *configutil.Telemetry) error return nil } -func (b *Server) PrintInfo(ui cli.Ui, mode string) { +func (b *Server) PrintInfo(ui cli.Ui) { b.InfoKeys = append(b.InfoKeys, "version") verInfo := version.Get() b.Info["version"] = verInfo.FullVersionNumber(false) @@ -221,7 +227,7 @@ func (b *Server) PrintInfo(ui cli.Ui, mode string) { } } sort.Strings(b.InfoKeys) - ui.Output(fmt.Sprintf("==> Boundary %s configuration:\n", mode)) + ui.Output(fmt.Sprintf("==> Boundary server configuration:\n")) for _, k := range b.InfoKeys { ui.Output(fmt.Sprintf( "%s%s: %s", @@ -233,7 +239,7 @@ func (b *Server) PrintInfo(ui cli.Ui, mode string) { // Output the header that the server has started if !b.CombineLogs { - ui.Output(fmt.Sprintf("==> Boundary %s started! Log data will stream in below:\n", mode)) + ui.Output(fmt.Sprintf("==> Boundary server started! Log data will stream in below:\n")) } } @@ -341,8 +347,8 @@ func (b *Server) SetupKMSes(ui cli.Ui, config *config.Config) error { return errors.New("KMS block missing 'purpose'") case "root", "worker-auth", "config": case "recovery": - if config.Controller != nil && config.Controller.DevRecoveryKey != "" { - kms.Config["key"] = config.Controller.DevRecoveryKey + if config.Controller != nil && config.DevRecoveryKey != "" { + kms.Config["key"] = config.DevRecoveryKey } default: return fmt.Errorf("Unknown KMS purpose %q", kms.Purpose) diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 624f51754..e4ef70d21 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -11,7 +11,6 @@ import ( "github.com/hashicorp/boundary/internal/cmd/commands/authmethods" "github.com/hashicorp/boundary/internal/cmd/commands/authtokens" "github.com/hashicorp/boundary/internal/cmd/commands/config" - "github.com/hashicorp/boundary/internal/cmd/commands/controller" "github.com/hashicorp/boundary/internal/cmd/commands/database" "github.com/hashicorp/boundary/internal/cmd/commands/dev" "github.com/hashicorp/boundary/internal/cmd/commands/groups" @@ -21,10 +20,10 @@ import ( "github.com/hashicorp/boundary/internal/cmd/commands/proxy" "github.com/hashicorp/boundary/internal/cmd/commands/roles" "github.com/hashicorp/boundary/internal/cmd/commands/scopes" + "github.com/hashicorp/boundary/internal/cmd/commands/server" "github.com/hashicorp/boundary/internal/cmd/commands/sessions" "github.com/hashicorp/boundary/internal/cmd/commands/targets" "github.com/hashicorp/boundary/internal/cmd/commands/users" - "github.com/hashicorp/boundary/internal/cmd/commands/worker" "github.com/mitchellh/cli" ) @@ -34,18 +33,8 @@ var Commands map[string]cli.CommandFactory func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Commands = map[string]cli.CommandFactory{ - "controller": func() (cli.Command, error) { - return &controller.Command{ - Server: base.NewServer(&base.Command{ - UI: serverCmdUi, - ShutdownCh: base.MakeShutdownCh(), - }), - SighupCh: MakeSighupCh(), - SigUSR2Ch: MakeSigUSR2Ch(), - }, nil - }, - "worker": func() (cli.Command, error) { - return &worker.Command{ + "server": func() (cli.Command, error) { + return &server.Command{ Server: base.NewServer(&base.Command{ UI: serverCmdUi, ShutdownCh: base.MakeShutdownCh(), diff --git a/internal/cmd/commands/database/init.go b/internal/cmd/commands/database/init.go index 603553cfb..2afdcb2a0 100644 --- a/internal/cmd/commands/database/init.go +++ b/internal/cmd/commands/database/init.go @@ -171,20 +171,20 @@ func (c *InitCommand) Run(args []string) int { "in a Docker container, provide the IPC_LOCK cap to the container.")) } - if c.Config.Database == nil { - c.UI.Error(`"database" config block not found`) + if c.Config.Controller.Database == nil { + c.UI.Error(`"controller.database" config block not found`) return 1 } - urlToParse := c.Config.Database.Url + urlToParse := c.Config.Controller.Database.Url if urlToParse == "" { c.UI.Error(`"url" not specified in "database" config block"`) return 1 } var migrationUrlToParse string - if c.Config.Database.MigrationUrl != "" { - migrationUrlToParse = c.Config.Database.MigrationUrl + if c.Config.Controller.Database.MigrationUrl != "" { + migrationUrlToParse = c.Config.Controller.Database.MigrationUrl } if c.flagMigrationUrl != "" && c.flagMigrationUrl != base.NotSetValue { migrationUrlToParse = c.flagMigrationUrl diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 7100eb5b6..85bb964c8 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -4,14 +4,15 @@ import ( "fmt" "runtime" "strings" - "sync" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/cmd/base" - controllercmd "github.com/hashicorp/boundary/internal/cmd/commands/controller" - workercmd "github.com/hashicorp/boundary/internal/cmd/commands/worker" "github.com/hashicorp/boundary/internal/cmd/config" + "github.com/hashicorp/boundary/internal/host/static" + "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/types/scope" "github.com/mitchellh/cli" "github.com/posener/complete" ) @@ -27,6 +28,10 @@ type Command struct { ReloadedCh chan struct{} SigUSR2Ch chan struct{} + Config *config.Config + controller *controller.Controller + worker *worker.Worker + flagLogLevel string flagLogFormat string flagCombineLogs bool @@ -181,9 +186,9 @@ func (c *Command) Run(args []string) int { return 1 } - childShutdownCh := make(chan struct{}) + //childShutdownCh := make(chan struct{}) - devConfig, err := config.DevCombined() + c.Config, err = config.DevCombined() if err != nil { c.UI.Error(fmt.Errorf("Error creating controller dev config: %w", err).Error()) return 1 @@ -198,6 +203,11 @@ func (c *Command) Run(args []string) int { return 1 } c.DevAuthMethodId = fmt.Sprintf("%s_%s", password.AuthMethodPrefix, 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) } if c.flagLoginName != "" { c.DevLoginName = c.flagLoginName @@ -206,11 +216,12 @@ func (c *Command) Run(args []string) int { c.DevPassword = c.flagPassword } - devConfig.PassthroughDirectory = c.FlagDevPassthroughDirectory + c.Config.PassthroughDirectory = c.flagPassthroughDirectory - for _, l := range devConfig.Listeners { + for _, l := range c.Config.Listeners { if len(l.Purpose) != 1 { - continue + c.UI.Error("Only one purpose supported for each listener") + return 1 } switch l.Purpose[0] { case "api": @@ -237,15 +248,15 @@ func (c *Command) Run(args []string) int { base.StartMemProfiler(c.Logger) - if err := c.SetupMetrics(c.UI, devConfig.Telemetry); err != nil { + if err := c.SetupMetrics(c.UI, c.Config.Telemetry); err != nil { c.UI.Error(err.Error()) return 1 } - if c.FlagDevRecoveryKey != "" { - devConfig.Controller.DevRecoveryKey = c.FlagDevRecoveryKey + if c.flagRecoveryKey != "" { + c.Config.DevRecoveryKey = c.flagRecoveryKey } - if err := c.SetupKMSes(c.UI, devConfig); err != nil { + if err := c.SetupKMSes(c.UI, c.Config); err != nil { c.UI.Error(err.Error()) return 1 } @@ -258,27 +269,27 @@ func (c *Command) Run(args []string) int { return 1 } c.InfoKeys = append(c.InfoKeys, "[Controller] AEAD Key Bytes") - c.Info["[Controller] AEAD Key Bytes"] = devConfig.Controller.DevControllerKey + c.Info["[Controller] AEAD Key Bytes"] = c.Config.DevControllerKey c.InfoKeys = append(c.InfoKeys, "[Worker-Auth] AEAD Key Bytes") - c.Info["[Worker-Auth] AEAD Key Bytes"] = devConfig.Controller.DevWorkerAuthKey + c.Info["[Worker-Auth] AEAD Key Bytes"] = c.Config.DevWorkerAuthKey c.InfoKeys = append(c.InfoKeys, "[Recovery] AEAD Key Bytes") - c.Info["[Recovery] AEAD Key Bytes"] = devConfig.Controller.DevRecoveryKey + c.Info["[Recovery] AEAD Key Bytes"] = c.Config.DevRecoveryKey // Initialize the listeners - if err := c.SetupListeners(c.UI, devConfig.SharedConfig, []string{"api", "cluster", "proxy"}); err != nil { + if err := c.SetupListeners(c.UI, c.Config.SharedConfig, []string{"api", "cluster", "proxy"}); err != nil { c.UI.Error(err.Error()) return 1 } - if err := c.SetupWorkerPublicAddress(devConfig, c.flagWorkerPublicAddr); err != nil { + if err := c.SetupWorkerPublicAddress(c.Config, c.flagWorkerPublicAddr); err != nil { c.UI.Error(err.Error()) return 1 } c.InfoKeys = append(c.InfoKeys, "worker public addr") - c.Info["worker public addr"] = devConfig.Worker.PublicAddr + c.Info["worker public addr"] = c.Config.Worker.PublicAddr // Write out the PID to the file now that server has successfully started - if err := c.StorePidFile(devConfig.PidFile); err != nil { + if err := c.StorePidFile(c.Config.PidFile); err != nil { c.UI.Error(fmt.Errorf("Error storing PID: %w", err).Error()) return 1 } @@ -298,47 +309,97 @@ func (c *Command) Run(args []string) int { c.ShutdownFuncs = append(c.ShutdownFuncs, c.DestroyDevDatabase) } - c.PrintInfo(c.UI, "dev mode") + c.PrintInfo(c.UI) c.ReleaseLogGate() - // Instantiate the wait group - shutdownWg := &sync.WaitGroup{} - shutdownWg.Add(2) - controllerSighupCh := make(chan struct{}) - c.childSighupCh = append(c.childSighupCh, controllerSighupCh) + { + conf := &controller.Config{ + RawConfig: c.Config, + Server: c.Server, + } - devController := &controllercmd.Command{ - Server: c.Server, - ExtShutdownCh: childShutdownCh, - SighupCh: controllerSighupCh, - Config: devConfig, - } - if err := devController.Start(); err != nil { - c.UI.Error(err.Error()) - return 1 - } + var err error + c.controller, err = controller.New(conf) + if err != nil { + c.UI.Error(fmt.Errorf("Error initializing controller: %w", 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: devConfig, - } - if err := devWorker.Start(); err != nil { - c.UI.Error(err.Error()) - return 1 + if err := c.controller.Start(); err != nil { + retErr := fmt.Errorf("Error starting controller: %w", err) + if err := c.controller.Shutdown(false); err != nil { + c.UI.Error(retErr.Error()) + retErr = fmt.Errorf("Error shutting down controller: %w", err) + } + c.UI.Error(retErr.Error()) + return 1 + } } + { + conf := &worker.Config{ + RawConfig: c.Config, + Server: c.Server, + } - go func() { - defer shutdownWg.Done() - devController.WaitForInterrupt() - }() - go func() { - defer shutdownWg.Done() - devWorker.WaitForInterrupt() - }() + var err error + c.worker, err = worker.New(conf) + if err != nil { + c.UI.Error(fmt.Errorf("Error initializing controller: %w", err).Error()) + return 1 + } + + if err := c.worker.Start(); err != nil { + retErr := fmt.Errorf("Error starting worker: %w", err) + if err := c.worker.Shutdown(false); err != nil { + c.UI.Error(retErr.Error()) + retErr = fmt.Errorf("Error shutting down worker: %w", err) + } + c.UI.Error(retErr.Error()) + if err := c.controller.Shutdown(false); err != nil { + c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error()) + } + 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 @@ -348,16 +409,27 @@ func (c *Command) Run(args []string) int { case <-c.ShutdownCh: c.UI.Output("==> Boundary dev environment shutdown triggered") - childShutdownCh <- struct{}{} - childShutdownCh <- struct{}{} + /* + childShutdownCh <- struct{}{} + childShutdownCh <- struct{}{} + */ + if err := c.worker.Shutdown(false); err != nil { + c.UI.Error(fmt.Errorf("Error shutting down worker: %w", err).Error()) + } + + if err := c.controller.Shutdown(false); err != nil { + c.UI.Error(fmt.Errorf("Error shutting down controller: %w", err).Error()) + } shutdownTriggered = true - case <-c.SighupCh: - c.UI.Output("==> Boundary dev environment reload triggered") - for _, v := range c.childSighupCh { - v <- struct{}{} - } + /* + 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) @@ -366,7 +438,7 @@ func (c *Command) Run(args []string) int { } } - shutdownWg.Wait() + //shutdownWg.Wait() return 0 } diff --git a/internal/cmd/commands/controller/controller.go b/internal/cmd/commands/server/server.go similarity index 70% rename from internal/cmd/commands/controller/controller.go rename to internal/cmd/commands/server/server.go index d457058fe..53baf2fc8 100644 --- a/internal/cmd/commands/controller/controller.go +++ b/internal/cmd/commands/server/server.go @@ -1,4 +1,4 @@ -package controller +package server import ( "fmt" @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/servers/controller" - "github.com/hashicorp/boundary/sdk/strutil" + "github.com/hashicorp/boundary/internal/servers/worker" "github.com/hashicorp/boundary/sdk/wrapper" "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping" @@ -32,6 +32,7 @@ type Command struct { Config *config.Config controller *controller.Controller + worker *worker.Worker configWrapper wrapping.Wrapper @@ -43,16 +44,16 @@ type Command struct { } func (c *Command) Synopsis() string { - return "Start a Boundary controller" + return "Start a Boundary server" } func (c *Command) Help() string { helpText := ` -Usage: boundary controller [options] +Usage: boundary server [options] - Start a controller with a configuration file: + Start a server (controller, worker, or both) with a configuration file: - $ boundary controller -config=/etc/boundary/controller.hcl + $ boundary server -config=/etc/boundary/controller.hcl For a full list of examples, please see the documentation. @@ -141,16 +142,15 @@ func (c *Command) Run(args []string) int { return 1 } - if c.FlagDevRecoveryKey != "" { - c.Config.Controller.DevRecoveryKey = c.FlagDevRecoveryKey - } if err := c.SetupKMSes(c.UI, c.Config); err != nil { c.UI.Error(err.Error()) return 1 } - if c.RootKms == nil { - c.UI.Error("Root KMS not found after parsing KMS blocks") - return 1 + if c.Config.Controller != nil { + if c.RootKms == nil { + c.UI.Error("Root KMS not found after parsing KMS blocks") + return 1 + } } if c.WorkerAuthKms == nil { c.UI.Error("Worker Auth KMS not found after parsing KMS blocks") @@ -175,67 +175,84 @@ func (c *Command) Run(args []string) int { // Perform controller-specific listener checks here before setup var foundCluster bool - var foundAPI bool + var foundApi bool + var foundProxy bool for _, lnConfig := range c.Config.Listeners { switch len(lnConfig.Purpose) { + case 0: + c.UI.Error("Listener specified without a purpose") + return 1 + case 1: - switch lnConfig.Purpose[0] { + purpose := lnConfig.Purpose[0] + switch purpose { case "cluster": foundCluster = true case "api": - foundAPI = true + foundApi = true case "proxy": - // Do nothing, in a dev mode we might see it here + foundProxy = true default: c.UI.Error(fmt.Sprintf("Unknown listener purpose %q", lnConfig.Purpose[0])) return 1 } - case 0: - lnConfig.Purpose = []string{"api", "cluster"} - fallthrough - - case 2: - if !strutil.StrListContains(lnConfig.Purpose, "api") || !strutil.StrListContains(lnConfig.Purpose, "cluster") { - c.UI.Error(fmt.Sprintf("Invalid listener purpose set: %v", lnConfig.Purpose)) - return 1 - } - if lnConfig.TLSDisable { - c.UI.Error(fmt.Sprintf("TLS cannot be disabled on listener when serving both %q and %q purposes", "api", "cluster")) - return 1 - } - foundAPI = true - foundCluster = true + default: + c.UI.Error("Specifying a listener with more than one purpose is not supported") + return 1 } } - if foundAPI && !foundCluster { - c.UI.Error("No listener marked for cluster purpose found, but listener explicitly marked for api was found") - return 1 + if c.Config.Controller != nil { + if !foundApi { + c.UI.Error(`Config activates controller but no listener with "api" purpose found`) + return 1 + } + if !foundCluster { + c.UI.Error(`Config activates controller but no listener with "cluster" purpose found`) + return 1 + } } - if err := c.SetupListeners(c.UI, c.Config.SharedConfig, []string{"api", "cluster"}); err != nil { + if c.Config.Worker != nil { + if !foundProxy { + c.UI.Error(`Config activates worker but no listener with "proxy" purpose found`) + return 1 + } + } + if err := c.SetupListeners(c.UI, c.Config.SharedConfig, []string{"api", "cluster", "proxy"}); err != nil { c.UI.Error(err.Error()) return 1 } + if c.Config.Worker != nil { + if err := c.SetupWorkerPublicAddress(c.Config, ""); err != nil { + c.UI.Error(err.Error()) + return 1 + } + c.InfoKeys = append(c.InfoKeys, "public addr") + c.Info["public addr"] = c.Config.Worker.PublicAddr + } + // Write out the PID to the file now that server has successfully started if err := c.StorePidFile(c.Config.PidFile); err != nil { c.UI.Error(fmt.Errorf("Error storing PID: %w", err).Error()) return 1 } - if c.Config.Database == nil || c.Config.Database.Url == "" { - c.UI.Error(`"url" not specified in "database" config block"`) - return 1 - } - dbaseUrl, err := config.ParseAddress(c.Config.Database.Url) - if err != nil && err != config.ErrNotAUrl { - c.UI.Error(fmt.Errorf("Error parsing database url: %w", err).Error()) - return 1 - } - c.DatabaseUrl = strings.TrimSpace(dbaseUrl) - if err := c.ConnectToDatabase("postgres"); err != nil { - c.UI.Error(fmt.Errorf("Error connecting to database: %w", err).Error()) - return 1 + if c.Config.Controller != nil { + if c.Config.Controller.Database == nil || c.Config.Controller.Database.Url == "" { + c.UI.Error(`"url" not specified in "controller.database" config block"`) + return 1 + } + dbaseUrl, err := config.ParseAddress(c.Config.Controller.Database.Url) + if err != nil && err != config.ErrNotAUrl { + c.UI.Error(fmt.Errorf("Error parsing database url: %w", err).Error()) + return 1 + } + c.DatabaseUrl = strings.TrimSpace(dbaseUrl) + if err := c.ConnectToDatabase("postgres"); err != nil { + c.UI.Error(fmt.Errorf("Error connecting to database: %w", err).Error()) + return 1 + } } defer func() { @@ -244,12 +261,24 @@ func (c *Command) Run(args []string) int { } }() - c.PrintInfo(c.UI, "controller") + c.PrintInfo(c.UI) c.ReleaseLogGate() - if err := c.Start(); err != nil { - c.UI.Error(err.Error()) - return 1 + if c.Config.Controller != nil { + if err := c.StartController(); err != nil { + c.UI.Error(err.Error()) + return 1 + } + } + + if c.Config.Worker != nil { + if err := c.StartWorker(); err != nil { + c.UI.Error(err.Error()) + if err := c.controller.Shutdown(false); err != nil { + c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error()) + } + return 1 + } } return c.WaitForInterrupt() @@ -293,17 +322,28 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { return 1 } + if c.Config.Controller == nil && c.Config.Worker == nil { + c.UI.Error("Neither worker nor controller specified in configuration file.") + return 1 + } + if c.Config.Controller != nil && c.Config.Controller.Name == "" { + c.UI.Error("Controller has no name set. It must be the unique name of this instance.") + return 1 + } + if c.Config.Worker != nil && c.Config.Worker.Name == "" { + c.UI.Error("Worker has no name set. It must be the unique name of this instance.") + return 1 + } + return 0 } -func (c *Command) Start() error { - // Instantiate the wait group +func (c *Command) StartController() error { conf := &controller.Config{ RawConfig: c.Config, Server: c.Server, } - // Initialize the core var err error c.controller, err = controller.New(conf) if err != nil { @@ -314,7 +354,31 @@ func (c *Command) Start() error { retErr := fmt.Errorf("Error starting controller: %w", err) if err := c.controller.Shutdown(false); err != nil { c.UI.Error(retErr.Error()) - retErr = fmt.Errorf("Error with controller shutdown: %w", err) + retErr = fmt.Errorf("Error shutting down controller: %w", err) + } + return retErr + } + + return nil +} + +func (c *Command) StartWorker() error { + conf := &worker.Config{ + RawConfig: c.Config, + Server: c.Server, + } + + var err error + c.worker, err = worker.New(conf) + if err != nil { + return fmt.Errorf("Error initializing worker: %w", err) + } + + if err := c.worker.Start(); err != nil { + retErr := fmt.Errorf("Error starting worker: %w", err) + if err := c.worker.Shutdown(false); err != nil { + c.UI.Error(retErr.Error()) + retErr = fmt.Errorf("Error shutting down worker: %w", err) } return retErr } @@ -334,10 +398,18 @@ func (c *Command) WaitForInterrupt() int { for !shutdownTriggered { select { case <-shutdownCh: - c.UI.Output("==> Boundary controller shutdown triggered") + c.UI.Output("==> Boundary server shutdown triggered") - if err := c.controller.Shutdown(false); err != nil { - c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error()) + if c.Config.Worker != nil { + if err := c.worker.Shutdown(false); err != nil { + c.UI.Error(fmt.Errorf("Error shutting down worker: %w", err).Error()) + } + } + + if c.Config.Controller != nil { + if err := c.controller.Shutdown(c.Config.Worker != nil); err != nil { + c.UI.Error(fmt.Errorf("Error shutting down controller: %w", err).Error()) + } } shutdownTriggered = true diff --git a/internal/cmd/commands/worker/worker.go b/internal/cmd/commands/worker/worker.go deleted file mode 100644 index 40d619605..000000000 --- a/internal/cmd/commands/worker/worker.go +++ /dev/null @@ -1,381 +0,0 @@ -package worker - -import ( - "fmt" - "runtime" - "strings" - "sync" - - "github.com/hashicorp/boundary/internal/cmd/base" - "github.com/hashicorp/boundary/internal/cmd/config" - "github.com/hashicorp/boundary/internal/servers/worker" - "github.com/hashicorp/boundary/sdk/wrapper" - "github.com/hashicorp/go-hclog" - wrapping "github.com/hashicorp/go-kms-wrapping" - "github.com/hashicorp/go-multierror" - "github.com/hashicorp/vault/sdk/helper/mlock" - "github.com/mitchellh/cli" - "github.com/posener/complete" -) - -var _ cli.Command = (*Command)(nil) -var _ cli.CommandAutocomplete = (*Command)(nil) - -type Command struct { - *base.Server - - ExtShutdownCh chan struct{} - SighupCh chan struct{} - ReloadedCh chan struct{} - SigUSR2Ch chan struct{} - - cleanupGuard sync.Once - - Config *config.Config - worker *worker.Worker - - configWrapper wrapping.Wrapper - - flagConfig string - flagConfigKms string - flagLogLevel string - flagLogFormat string - flagCombineLogs bool -} - -func (c *Command) Synopsis() string { - return "Start a Boundary worker" -} - -func (c *Command) Help() string { - helpText := ` -Usage: boundary worker [options] - - Start a worker with a configuration file: - - $ boundary worker -config=/etc/boundary/worker.hcl - - For a full list of examples, please see the documentation. - -` + c.Flags().Help() - return strings.TrimSpace(helpText) -} - -func (c *Command) Flags() *base.FlagSets { - set := c.FlagSet(base.FlagSetNone) - - f := set.NewFlagSet("Command Options") - - f.StringVar(&base.StringVar{ - Name: "config", - Target: &c.flagConfig, - Completion: complete.PredictOr( - complete.PredictFiles("*.hcl"), - complete.PredictFiles("*.json"), - ), - Usage: "Path to a configuration file.", - }) - - f.StringVar(&base.StringVar{ - Name: "config-kms", - Target: &c.flagConfigKms, - Completion: complete.PredictOr( - complete.PredictFiles("*.hcl"), - complete.PredictFiles("*.json"), - ), - Usage: `Path to a configuration file containing a "kms" block marked for "config" purpose, to perform decryption of the main configuration file. If not set, will look for such a block in the main configuration file, which has some drawbacks; see the help output for "boundary config encrypt -h" for details.`, - }) - - f.StringVar(&base.StringVar{ - Name: "log-level", - Target: &c.flagLogLevel, - Default: base.NotSetValue, - EnvVar: "BOUNDARY_LOG_LEVEL", - Completion: complete.PredictSet("trace", "debug", "info", "warn", "err"), - Usage: "Log verbosity level. Supported values (in order of more detail to less) are " + - "\"trace\", \"debug\", \"info\", \"warn\", and \"err\".", - }) - - f.StringVar(&base.StringVar{ - Name: "log-format", - Target: &c.flagLogFormat, - Default: base.NotSetValue, - Completion: complete.PredictSet("standard", "json"), - Usage: `Log format. Supported values are "standard" and "json".`, - }) - - f.BoolVar(&base.BoolVar{ - Name: "combine-logs", - Target: &c.flagCombineLogs, - Default: false, - Usage: "If set, both startup information and logs will be sent to stdout. If not set (the default), startup information will go to stdout and logs will be sent to stderr.", - }) - - return set -} - -func (c *Command) AutocompleteArgs() complete.Predictor { - return complete.PredictNothing -} - -func (c *Command) AutocompleteFlags() complete.Flags { - return c.Flags().Completions() -} - -func (c *Command) Run(args []string) int { - c.CombineLogs = c.flagCombineLogs - - if result := c.ParseFlagsAndConfig(args); result > 0 { - return result - } - - if c.configWrapper != nil { - defer func() { - if err := c.configWrapper.Finalize(c.Context); err != nil { - c.UI.Warn(fmt.Errorf("Error finalizing config kms: %w", err).Error()) - } - }() - } - - if err := c.SetupLogging(c.flagLogLevel, c.flagLogFormat, c.Config.LogLevel, c.Config.LogFormat); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - base.StartMemProfiler(c.Logger) - - if err := c.SetupMetrics(c.UI, c.Config.Telemetry); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if err := c.SetupKMSes(c.UI, c.Config); err != nil { - c.UI.Error(err.Error()) - return 1 - } - if c.WorkerAuthKms == nil { - c.UI.Error("Worker Auth KMS not found after parsing KMS blocks") - return 1 - } - - // If mlockall(2) isn't supported, show a warning. We disable this in dev - // because it is quite scary to see when first using Boundary. We also disable - // this if the user has explicitly disabled mlock in configuration. - if !c.Config.DisableMlock && !mlock.Supported() { - c.UI.Warn(base.WrapAtLength( - "WARNING! mlock is not supported on this system! An mlockall(2)-like " + - "syscall to prevent memory from being swapped to disk is not " + - "supported on this system. For better security, only run Boundary on " + - "systems where this call is supported. If you are running Boundary" + - "in a Docker container, provide the IPC_LOCK cap to the container.")) - } - - if err := c.SetupListeners(c.UI, c.Config.SharedConfig, []string{"proxy"}); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - if err := c.SetupWorkerPublicAddress(c.Config, ""); err != nil { - c.UI.Error(err.Error()) - return 1 - } - c.InfoKeys = append(c.InfoKeys, "public addr") - c.Info["public addr"] = c.Config.Worker.PublicAddr - - // Write out the PID to the file now that server has successfully started - if err := c.StorePidFile(c.Config.PidFile); err != nil { - c.UI.Error(fmt.Errorf("Error storing PID: %w", err).Error()) - return 1 - } - - defer func() { - if err := c.RunShutdownFuncs(); err != nil { - c.UI.Error(fmt.Errorf("Error running shutdown tasks: %w", err).Error()) - } - }() - - c.PrintInfo(c.UI, "worker") - c.ReleaseLogGate() - - if err := c.Start(); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - return c.WaitForInterrupt() -} - -func (c *Command) ParseFlagsAndConfig(args []string) int { - var err error - - f := c.Flags() - - if err = f.Parse(args); err != nil { - c.UI.Error(err.Error()) - return 1 - } - - wrapperPath := c.flagConfig - if c.flagConfigKms != "" { - wrapperPath = c.flagConfigKms - } - wrapper, err := wrapper.GetWrapperFromPath(wrapperPath, "config") - if err != nil { - c.UI.Error(err.Error()) - return 1 - } - if wrapper != nil { - c.configWrapper = wrapper - if err := wrapper.Init(c.Context); err != nil { - c.UI.Error(fmt.Errorf("Could not initialize kms: %w", err).Error()) - return 1 - } - } - - // Validation - if len(c.flagConfig) == 0 { - c.UI.Error("Must supply a config file with -config") - return 1 - } - c.Config, err = config.LoadFile(c.flagConfig, wrapper) - if err != nil { - c.UI.Error("Error parsing config: " + err.Error()) - return 1 - } - - return 0 -} - -func (c *Command) Start() error { - // Instantiate the wait group - conf := &worker.Config{ - RawConfig: c.Config, - Server: c.Server, - } - - // Initialize the core - var err error - c.worker, err = worker.New(conf) - if err != nil { - return fmt.Errorf("Error initializing worker: %w", err) - } - - if err := c.worker.Start(); err != nil { - retErr := fmt.Errorf("Error starting worker: %w", err) - if err := c.worker.Shutdown(false); err != nil { - c.UI.Error(retErr.Error()) - retErr = fmt.Errorf("Error with worker shutdown: %w", err) - } - return retErr - } - - return nil -} - -func (c *Command) WaitForInterrupt() int { - // Wait for shutdown - shutdownTriggered := false - - shutdownCh := c.ShutdownCh - if c.ExtShutdownCh != nil { - shutdownCh = c.ExtShutdownCh - } - - for !shutdownTriggered { - select { - case <-shutdownCh: - c.UI.Output("==> Boundary worker shutdown triggered") - - if err := c.worker.Shutdown(false); err != nil { - c.UI.Error(fmt.Errorf("Error with worker shutdown: %w", err).Error()) - } - - shutdownTriggered = true - - case <-c.SighupCh: - c.UI.Output("==> Boundary worker reload triggered") - - // Check for new log level - var level hclog.Level - var err error - var newConf *config.Config - - if c.flagConfig == "" { - goto RUNRELOADFUNCS - } - - newConf, err = config.LoadFile(c.flagConfig, c.configWrapper) - if err != nil { - c.Logger.Error("could not reload config", "path", c.flagConfig, "error", err) - goto RUNRELOADFUNCS - } - - // Ensure at least one config was found. - if newConf == nil { - c.Logger.Error("no config found at reload time") - goto RUNRELOADFUNCS - } - - if newConf.LogLevel != "" { - configLogLevel := strings.ToLower(strings.TrimSpace(newConf.LogLevel)) - switch configLogLevel { - case "trace": - level = hclog.Trace - case "debug": - level = hclog.Debug - case "notice", "info", "": - level = hclog.Info - case "warn", "warning": - level = hclog.Warn - case "err", "error": - level = hclog.Error - default: - c.Logger.Error("unknown log level found on reload", "level", newConf.LogLevel) - goto RUNRELOADFUNCS - } - c.Logger.SetLevel(level) - } - - RUNRELOADFUNCS: - if err := c.Reload(); err != nil { - c.UI.Error(fmt.Errorf("Error(s) were encountered during worker reload: %w", err).Error()) - } - - case <-c.SigUSR2Ch: - buf := make([]byte, 32*1024*1024) - n := runtime.Stack(buf[:], true) - c.Logger.Info("goroutine trace", "stack", string(buf[:n])) - } - } - - return 0 -} - -func (c *Command) Reload() error { - c.ReloadFuncsLock.RLock() - defer c.ReloadFuncsLock.RUnlock() - - var reloadErrors *multierror.Error - - for k, relFuncs := range c.ReloadFuncs { - switch { - case strings.HasPrefix(k, "listener|"): - for _, relFunc := range relFuncs { - if relFunc != nil { - if err := relFunc(); err != nil { - reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("error encountered reloading listener: %w", err)) - } - } - } - } - } - - // Send a message that we reloaded. This prevents "guessing" sleep times - // in tests. - select { - case c.ReloadedCh <- struct{}{}: - default: - } - - return reloadErrors.ErrorOrNil() -} diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index 27759a589..3ae8c68a0 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -65,7 +65,6 @@ listener "tcp" { listener "tcp" { purpose = "cluster" - tls_disable = true proxy_protocol_behavior = "allow_authorized" proxy_protocol_authorized_addrs = "127.0.0.1" } @@ -74,7 +73,6 @@ listener "tcp" { devWorkerExtraConfig = ` listener "tcp" { purpose = "proxy" - tls_disable = true proxy_protocol_behavior = "allow_authorized" proxy_protocol_authorized_addrs = "127.0.0.1" } @@ -91,19 +89,21 @@ worker { type Config struct { *configutil.SharedConfig `hcl:"-"` - DevController bool `hcl:"-"` - PassthroughDirectory string `hcl:"-"` - Worker *Worker `hcl:"worker"` - Controller *Controller `hcl:"controller"` - Database *Database `hcl:"database"` + Worker *Worker `hcl:"worker"` + Controller *Controller `hcl:"controller"` + + // Dev-related options + DevController bool `hcl:"-"` + PassthroughDirectory string `hcl:"-"` + DevControllerKey string `hcl:"-"` + DevWorkerAuthKey string `hcl:"-"` + DevRecoveryKey string `hcl:"-"` } type Controller struct { - Name string `hcl:"name"` - Description string `hcl:"description"` - DevControllerKey string `hcl:"-"` - DevWorkerAuthKey string `hcl:"-"` - DevRecoveryKey string `hcl:"-"` + Name string `hcl:"name"` + Description string `hcl:"description"` + Database *Database `hcl:"database"` } type Worker struct { @@ -159,9 +159,9 @@ func DevController() (*Config, error) { return nil, fmt.Errorf("error parsing dev config: %w", err) } parsed.DevController = true - parsed.Controller.DevControllerKey = controllerKey - parsed.Controller.DevWorkerAuthKey = workerAuthKey - parsed.Controller.DevRecoveryKey = recoveryKey + parsed.DevControllerKey = controllerKey + parsed.DevWorkerAuthKey = workerAuthKey + parsed.DevRecoveryKey = recoveryKey return parsed, nil } @@ -173,9 +173,9 @@ func DevCombined() (*Config, error) { return nil, fmt.Errorf("error parsing dev config: %w", err) } parsed.DevController = true - parsed.Controller.DevControllerKey = controllerKey - parsed.Controller.DevWorkerAuthKey = workerAuthKey - parsed.Controller.DevRecoveryKey = recoveryKey + parsed.DevControllerKey = controllerKey + parsed.DevWorkerAuthKey = workerAuthKey + parsed.DevRecoveryKey = recoveryKey return parsed, nil } diff --git a/internal/cmd/config/config_test.go b/internal/cmd/config/config_test.go index 6994f408e..2b391c976 100644 --- a/internal/cmd/config/config_test.go +++ b/internal/cmd/config/config_test.go @@ -90,9 +90,9 @@ func TestDevController(t *testing.T) { exp.Seals[0].Config["key"] = actual.Seals[0].Config["key"] exp.Seals[1].Config["key"] = actual.Seals[1].Config["key"] exp.Seals[2].Config["key"] = actual.Seals[2].Config["key"] - exp.Controller.DevControllerKey = actual.Seals[0].Config["key"] - exp.Controller.DevWorkerAuthKey = actual.Seals[1].Config["key"] - exp.Controller.DevRecoveryKey = actual.Seals[2].Config["key"] + exp.DevControllerKey = actual.Seals[0].Config["key"] + exp.DevWorkerAuthKey = actual.Seals[1].Config["key"] + exp.DevRecoveryKey = actual.Seals[2].Config["key"] assert.Equal(t, exp, actual) }