diff --git a/.gitignore b/.gitignore index 25e9fb67fb..698cf0b4b1 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,6 @@ internal/ui/assets.go update-ui-assets* # Test config file -test.hcl +test*.hcl # vim: set filetype=conf : diff --git a/internal/cmd/base/base.go b/internal/cmd/base/base.go index e5ebbaea29..6bdedfac75 100644 --- a/internal/cmd/base/base.go +++ b/internal/cmd/base/base.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/authtokens" + "github.com/hashicorp/boundary/internal/wrapper" "github.com/hashicorp/boundary/recovery" "github.com/mitchellh/cli" "github.com/pkg/errors" @@ -183,10 +184,13 @@ func (c *Command) Client(opt ...Option) (*api.Client, error) { tokenName := "default" switch { case c.FlagRecoveryConfig != "": - wrapper, err := recovery.GetWrapper(c.Context, c.FlagRecoveryConfig) + wrapper, err := wrapper.GetWrapper(c.FlagRecoveryConfig, "recovery") if err != nil { return nil, err } + if wrapper == nil { + return nil, errors.New(`No "kms" block with purpose "recovery" found`) + } if err := wrapper.Init(c.Context); err != nil { return nil, fmt.Errorf("Error initializing kms: %w", err) } @@ -284,11 +288,6 @@ func (c *Command) FlagSet(bit FlagSetBit) *FlagSets { c.flagsOnce.Do(func() { set := NewFlagSets(c.UI) - // These flag sets will apply to all leaf subcommands. - // TODO: Optional, but FlagSetHTTP can be safely removed from the individual - // Flags() subcommands. - bit = bit | FlagSetHTTP - if bit&FlagSetHTTP != 0 { f := set.NewFlagSet("Connection Options") diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index 8b4792f161..e294121ef3 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -213,7 +213,13 @@ func (b *Server) PrintInfo(ui cli.Ui, mode string) { } // Server configuration output - padding := 36 + padding := 0 + for _, k := range b.InfoKeys { + currPadding := padding - len(k) + if currPadding < 2 { + padding = len(k) + 2 + } + } sort.Strings(b.InfoKeys) ui.Output(fmt.Sprintf("==> Boundary %s configuration:\n", mode)) for _, k := range b.InfoKeys { diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 02525a1ad0..86a176ffd4 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -144,12 +144,13 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { "config encrypt": func() (cli.Command, error) { return &config.EncryptDecryptCommand{ Command: base.NewCommand(ui), - Encrypt: true, + Func: "encrypt", }, nil }, "config decrypt": func() (cli.Command, error) { return &config.EncryptDecryptCommand{ Command: base.NewCommand(ui), + Func: "decrypt", }, nil }, diff --git a/internal/cmd/commands/config/config.go b/internal/cmd/commands/config/config.go index 89855b1f84..9ab0d00d88 100644 --- a/internal/cmd/commands/config/config.go +++ b/internal/cmd/commands/config/config.go @@ -1,8 +1,6 @@ package config import ( - "strings" - "github.com/hashicorp/boundary/internal/cmd/base" "github.com/mitchellh/cli" ) @@ -18,23 +16,21 @@ func (c *Command) Synopsis() string { } func (c *Command) Help() string { - helpText := ` -Usage: boundary config [options] [args] - - This command groups subcommands for operators interacting with Boundary's - config files. Here are a few examples of config commands: - - Encrypt sensitive values in a config file: - - $ boundary config encrypt config.hcl - - Decrypt sensitive values in a config file: - - $ boundary config decrypt config.hcl - - Please see the individual subcommand help for detailed usage information.` - - return strings.TrimSpace(helpText) + return base.WrapForHelpText([]string{ + "Usage: boundary config [options] [args]", + "", + " This command groups subcommands for operators interacting with Boundary's config files. Here are a few examples of config commands:", + "", + " Encrypt sensitive values in a config file:", + "", + " $ boundary config encrypt config.hcl", + "", + " Decrypt sensitive values in a config file:", + "", + " $ boundary config decrypt config.hcl", + "", + " Please see the individual subcommand help for detailed usage information.", + }) } func (c *Command) Run(args []string) int { diff --git a/internal/cmd/commands/config/encryptdecrypt.go b/internal/cmd/commands/config/encryptdecrypt.go index f00e359ea7..8fcc2e295a 100644 --- a/internal/cmd/commands/config/encryptdecrypt.go +++ b/internal/cmd/commands/config/encryptdecrypt.go @@ -3,12 +3,13 @@ package config import ( "fmt" "io/ioutil" + "net/textproto" "os" "strings" "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/wrapper" "github.com/hashicorp/shared-secure-libs/configutil" - "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/mitchellh/cli" "github.com/posener/complete" ) @@ -18,49 +19,82 @@ var _ cli.CommandAutocomplete = (*EncryptDecryptCommand)(nil) type EncryptDecryptCommand struct { *base.Command - Encrypt bool + Func string + flagConfig string + flagConfigKms string flagOverwrite bool flagStrip bool } func (c *EncryptDecryptCommand) Synopsis() string { - dir := "Decrypts" - if c.Encrypt { - dir = "Encrypts" - } - return fmt.Sprintf("%s sensitive values in Vault's configuration file", dir) + return fmt.Sprintf("%s sensitive values in Boundary's configuration file", textproto.CanonicalMIMEHeaderKey(c.Func)) } func (c *EncryptDecryptCommand) Help() string { - subCmd := "Decrypt" - if c.Encrypt { - subCmd = "Encrypt" - } - helpText := ` -Usage: boundary config %s [options] [args] - - %s sensitive values in a Boundary's configuration file. These values must be marked - with {{%s()}} as appropriate. This can only be used with string parameters, and - the markers must be inside the quote marks delimiting the string; as an example: - - foo = "{{encrypt(bar)}}" - - By default this will print the new configuration out. To overwrite into the same - file use the -overwrite flag. - - $ boundary config %s -overwrite config.hcl - ` - helpText = fmt.Sprintf(helpText, strings.ToLower(subCmd), subCmd, strings.ToLower(subCmd), strings.ToLower(subCmd)) - - return strings.TrimSpace(helpText) + var args []string + args = append(args, + "Usage: boundary config {{func}} [options] [args]", + "", + " {{upperfunc}} sensitive values in a Boundary's configuration file. These values must be marked with {{{{func}}()}} as appropriate. Example:", + "", + ` foo = "{{encrypt(bar)}}"`, + "", + " By default this will print out the new configuration. To overwrite into the same file use the -overwrite flag.", + "", + " $ boundary config {{func}} -overwrite config.hcl", + "", + ` In order for this command to perform its task, a "kms" block must be defined within a configuration file. Example:`, + "", + ` kms "aead" {`, + ` purpose = "config"`, + ` aead_type = "aes-gcm"`, + ` key = "7xtkEoS5EXPbgynwd+dDLHopaCqK8cq0Rpep4eooaTs="`, + ` }`, + "", + ` The "kms" block can be defined in the configuration file or via the -config flag. If defined in the configuration file, only string parameters are supported, and the markers must be inside the quote marks delimiting the string. Additionally, if the block is defined inline, do NOT use an an "aead" block with the key defined in the configuration file as it provides no protection.`, + "", + "", + ) + + for i, line := range args { + args[i] = + strings.Replace( + strings.Replace( + line, "{{func}}", c.Func, -1, + ), + "{{upperfunc}}", textproto.CanonicalMIMEHeaderKey(c.Func), -1, + ) + } + + return base.WrapForHelpText(args) + c.Flags().Help() } func (c *EncryptDecryptCommand) Flags() *base.FlagSets { - set := c.FlagSet(0) + set := c.FlagSet(base.FlagSetNone) f := set.NewFlagSet("Command Options") + f.StringVar(&base.StringVar{ + Name: "config-kms", + Target: &c.flagConfigKms, + Completion: complete.PredictOr( + complete.PredictFiles("*.hcl"), + complete.PredictFiles("*.json"), + ), + Usage: `If specified, the given file will be parsed for a "kms" block with purpose "config" and will use it to perform the command. If not set, the command will expect a block inline with the configuration file, and will only be able to support quoted string parameters.`, + }) + + f.StringVar(&base.StringVar{ + Name: "config", + Target: &c.flagConfig, + Completion: complete.PredictOr( + complete.PredictFiles("*.hcl"), + complete.PredictFiles("*.json"), + ), + Usage: `The configuration file upon which to perform encryption or decryption`, + }) + f.BoolVar(&base.BoolVar{ Name: "overwrite", Target: &c.flagOverwrite, @@ -85,73 +119,61 @@ func (c *EncryptDecryptCommand) AutocompleteFlags() complete.Flags { } func (c *EncryptDecryptCommand) Run(args []string) (ret int) { - op := "decrypt" - if c.Encrypt { - op = "encrypt" - } - f := c.Flags() if err := f.Parse(args); err != nil { c.UI.Error(err.Error()) return 1 } - path := "" args = f.Args() - switch len(args) { - case 1: - path = strings.TrimSpace(args[0]) - default: - c.UI.Error(fmt.Sprintf("Incorrect arguments (expected 1, got %d)", len(args))) + + switch c.flagConfig { + case "": + c.UI.Error(`Missing required parameter -config`) return 1 + default: + c.flagConfig = strings.TrimSpace(c.flagConfig) } - if path == "" { - c.UI.Error("A configuration file must be specified") - return 1 + kmsDefFile := c.flagConfig + + switch c.flagConfigKms { + case "": + default: + kmsDefFile = strings.TrimSpace(c.flagConfigKms) } - kmses, err := configutil.LoadConfigKMSes(path) + wrapper, err := wrapper.GetWrapper(kmsDefFile, "config") if err != nil { - c.UI.Error(fmt.Errorf("Error loading configuration from %s: %w", path, err).Error()) + c.UI.Error(err.Error()) return 1 } - - var kms *configutil.KMS - for _, v := range kmses { - if strutil.StrListContains(v.Purpose, "config") { - if kms != nil { - c.UI.Error("Only one kms block marked for \"config\" purpose is allowed") - return 1 - } - kms = v - } - } - if kms == nil { - c.UI.Error("No kms block with \"config\" purpose defined in the configuration file") + if wrapper == nil { + c.UI.Error(`No wrapper with "config" purpose found"`) return 1 } - d, err := ioutil.ReadFile(path) - if err != nil { - c.UI.Error(fmt.Errorf("Error reading config file: %w", err).Error()) + if err := wrapper.Init(c.Context); err != nil { + c.UI.Error(fmt.Errorf("Error initializing KMS: %w", err).Error()) return 1 } + defer func() { + if err := wrapper.Finalize(c.Context); err != nil { + c.UI.Warn(fmt.Errorf("Error encountered when finalizing KMS: %w", err).Error()) + } + }() - raw := string(d) - - wrapper, err := configutil.ConfigureWrapper(kms, nil, nil, nil) + d, err := ioutil.ReadFile(c.flagConfig) if err != nil { - c.UI.Error(fmt.Errorf("Error creating kms: %w", err).Error()) + c.UI.Error(fmt.Errorf("Error reading config file: %w", err).Error()) return 1 } - wrapper.Init(c.Context) - defer wrapper.Finalize(c.Context) + raw := string(d) - raw, err = configutil.EncryptDecrypt(raw, !c.Encrypt, c.flagStrip, wrapper) + raw, err = configutil.EncryptDecrypt(raw, c.Func == "decrypt", c.flagStrip, wrapper) if err != nil { - c.UI.Error(fmt.Errorf("Error %sing via kms: %w", op, err).Error()) + c.UI.Error(fmt.Errorf("Error %sing via kms: %w", c.Func, err).Error()) return 1 } @@ -160,7 +182,7 @@ func (c *EncryptDecryptCommand) Run(args []string) (ret int) { return 0 } - file, err := os.Create(path) + file, err := os.Create(c.flagConfig) if err != nil { c.UI.Error(fmt.Errorf("Error opening file for writing: %w", err).Error()) return 1 @@ -182,5 +204,5 @@ func (c *EncryptDecryptCommand) Run(args []string) (ret int) { c.UI.Error(fmt.Sprintf("Wrong number of bytes written to file, expected %d, wrote %d", len(raw), n)) } - return 0 + return } diff --git a/internal/cmd/commands/config/encryptdecrypt_test.go b/internal/cmd/commands/config/encryptdecrypt_test.go index d0646cf79a..9b59051307 100644 --- a/internal/cmd/commands/config/encryptdecrypt_test.go +++ b/internal/cmd/commands/config/encryptdecrypt_test.go @@ -15,31 +15,52 @@ import ( ) const ( - configEncryptPath = "./fixtures/configEncrypt.hcl" - configDecryptPath = "./fixtures/configDecrypt.hcl" + configEncryptPath = "./fixtures/config_encrypt.hcl" + configDecryptPath = "./fixtures/config_decrypt.hcl" + configKmsPath = "./fixtures/config_kms.hcl" + configExtEncryptPath = "./fixtures/config_ext_encrypt.hcl" + configExtEncryptStrippedPath = "./fixtures/config_ext_encrypt_stripped.hcl" + configExtDecryptPath = "./fixtures/config_ext_decrypt.hcl" ) func TestEncryptDecrypt(t *testing.T) { - cases := []struct { - encrypt bool - config string - exp string + f string + config string + configKms string + exp string + strip bool }{ { - encrypt: true, - config: configEncryptPath, - exp: "", + f: "encrypt", + config: configEncryptPath, + }, + { + f: "decrypt", + config: configDecryptPath, + exp: configEncryptPath, + }, + { + f: "encrypt-ext", + config: configExtEncryptPath, + configKms: configKmsPath, }, { - encrypt: false, - config: configDecryptPath, - exp: configEncryptPath, + f: "decrypt-ext", + config: configExtDecryptPath, + configKms: configKmsPath, + exp: configExtEncryptPath, + }, + { + f: "decrypt-ext-strip", + config: configExtDecryptPath, + configKms: configKmsPath, + exp: configExtEncryptStrippedPath, }, } for _, c := range cases { - t.Run(fmt.Sprintf("Test encrypt %v", c.encrypt), func(t *testing.T) { + t.Run(fmt.Sprintf("Test encrypt %v", c.f), func(t *testing.T) { var b bytes.Buffer @@ -51,9 +72,17 @@ func TestEncryptDecrypt(t *testing.T) { cmd := &EncryptDecryptCommand{ Command: base.NewCommand(ui), - Encrypt: c.encrypt} + Func: c.f, + } - if err := cmd.Run([]string{c.config}); err != 0 { + args := []string{"-config", c.config} + if c.configKms != "" { + args = append(args, "-config-kms", c.configKms) + } + if c.strip { + args = append(args, "-strip") + } + if err := cmd.Run(args); err != 0 { assert.Equal(t, err, 0) } @@ -64,7 +93,7 @@ func TestEncryptDecrypt(t *testing.T) { // If it's encrypting, then we can assume that it did the right thing since // there are many tests on the underlying codebase for that. If it's not // encrypting, compare it to the cleartext to verify because we can. - if !c.encrypt { + if c.f == "decrypt" { expected, err := ioutil.ReadFile(c.exp) assert.NoError(t, err) diff --git a/internal/cmd/commands/config/fixtures/configDecrypt.hcl b/internal/cmd/commands/config/fixtures/config_decrypt.hcl similarity index 100% rename from internal/cmd/commands/config/fixtures/configDecrypt.hcl rename to internal/cmd/commands/config/fixtures/config_decrypt.hcl diff --git a/internal/cmd/commands/config/fixtures/configEncrypt.hcl b/internal/cmd/commands/config/fixtures/config_encrypt.hcl similarity index 100% rename from internal/cmd/commands/config/fixtures/configEncrypt.hcl rename to internal/cmd/commands/config/fixtures/config_encrypt.hcl diff --git a/internal/cmd/commands/config/fixtures/config_ext_decrypt.hcl b/internal/cmd/commands/config/fixtures/config_ext_decrypt.hcl new file mode 100644 index 0000000000..731c386396 --- /dev/null +++ b/internal/cmd/commands/config/fixtures/config_ext_decrypt.hcl @@ -0,0 +1,7 @@ +int_val = {{decrypt(Ch7a69AX8R5w_cCZJUqLTQkWesuSHMrHxrRMMZnq53QqAA)}} +bool_val = {{decrypt(CiDURLrWUXLhEfkOemqqiQlcD_gsGsIx-kxVTIlVncN6-yoA)}} +kms "aead" { + purpose = "root" + aead_type = "aes-gcm" + key ="{{decrypt(CkiRTINwX19TnC3AB-zx5E133TXI9KzBWb8TxfVDrPb9m3Yfm9K99OkuJgRTj1rjmeMF-Kpl-0oouEc8_mNk6oPIqD8nUNvH3FYqAA)}}" +} \ No newline at end of file diff --git a/internal/cmd/commands/config/fixtures/config_ext_encrypt.hcl b/internal/cmd/commands/config/fixtures/config_ext_encrypt.hcl new file mode 100644 index 0000000000..7901b2a150 --- /dev/null +++ b/internal/cmd/commands/config/fixtures/config_ext_encrypt.hcl @@ -0,0 +1,7 @@ +int_val = {{encrypt(20)}} +bool_val = {{encrypt(true)}} +kms "aead" { + purpose = "root" + aead_type = "aes-gcm" + key ="{{encrypt(aA1hxJo0JUAqcIATx/r0QTjAGD/btCPechEsukI2bt0=)}}" +} \ No newline at end of file diff --git a/internal/cmd/commands/config/fixtures/config_ext_encrypt_stripped.hcl b/internal/cmd/commands/config/fixtures/config_ext_encrypt_stripped.hcl new file mode 100644 index 0000000000..4de3565eb7 --- /dev/null +++ b/internal/cmd/commands/config/fixtures/config_ext_encrypt_stripped.hcl @@ -0,0 +1,7 @@ +int_val = 20 +bool_val = true +kms "aead" { + purpose = "root" + aead_type = "aes-gcm" + key ="aA1hxJo0JUAqcIATx/r0QTjAGD/btCPechEsukI2bt0=)" +} \ No newline at end of file diff --git a/internal/cmd/commands/config/fixtures/config_kms.hcl b/internal/cmd/commands/config/fixtures/config_kms.hcl new file mode 100644 index 0000000000..188455cdb6 --- /dev/null +++ b/internal/cmd/commands/config/fixtures/config_kms.hcl @@ -0,0 +1,5 @@ +kms "aead" { + purpose = "config" + aead_type = "aes-gcm" + key = "c964AJj8VW8w4hKz/Jd8MvuLt0kkcjVuFqMiMvTvvN8=" +} \ No newline at end of file diff --git a/internal/cmd/commands/controller/controller.go b/internal/cmd/commands/controller/controller.go index 01c8eea7ed..affe9538fb 100644 --- a/internal/cmd/commands/controller/controller.go +++ b/internal/cmd/commands/controller/controller.go @@ -10,9 +10,10 @@ 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/internal/wrapper" "github.com/hashicorp/go-hclog" + wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-multierror" - "github.com/hashicorp/shared-secure-libs/configutil" "github.com/hashicorp/vault/sdk/helper/mlock" "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/mitchellh/cli" @@ -33,9 +34,10 @@ type Command struct { Config *config.Config controller *controller.Controller - configKMS *configutil.KMS + configWrapper wrapping.Wrapper flagConfig string + flagConfigKms string flagLogLevel string flagLogFormat string flagCombineLogs bool @@ -78,7 +80,17 @@ func (c *Command) Flags() *base.FlagSets { complete.PredictFiles("*.hcl"), complete.PredictFiles("*.json"), ), - Usage: "Path to a configuration file.", + Usage: "Path to the 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{ @@ -178,6 +190,14 @@ func (c *Command) Run(args []string) int { 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 @@ -317,19 +337,20 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { return 1 } - // preload the KMS for encrypting/decrypting the config parameters - kmss, err := configutil.LoadConfigKMSes(c.flagConfig) + wrapperPath := c.flagConfig + if c.flagConfigKms != "" { + wrapperPath = c.flagConfigKms + } + wrapper, err := wrapper.GetWrapper(wrapperPath, "config") if err != nil { - c.UI.Error("error loading KMS config: " + err.Error()) + c.UI.Error(err.Error()) return 1 } - - for _, kms := range kmss { - for _, purpose := range kms.Purpose { - if purpose == "config" { - c.configKMS = kms - break - } + 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 } } @@ -351,7 +372,7 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { c.flagDevLoginName = "" } - c.Config, err = config.LoadFile(c.flagConfig, c.configKMS) + c.Config, err = config.LoadFile(c.flagConfig, wrapper) if err != nil { c.UI.Error("Error parsing config: " + err.Error()) return 1 @@ -361,7 +382,7 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { if len(c.flagConfig) == 0 { c.Config, err = config.DevController() } else { - c.Config, err = config.LoadFile(c.flagConfig, c.configKMS) + c.Config, err = config.LoadFile(c.flagConfig, wrapper) } if err != nil { c.UI.Error(fmt.Errorf("Error creating dev config: %w", err).Error()) @@ -468,7 +489,7 @@ func (c *Command) WaitForInterrupt() int { goto RUNRELOADFUNCS } - newConf, err = config.LoadFile(c.flagConfig, c.configKMS) + 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 @@ -480,9 +501,6 @@ func (c *Command) WaitForInterrupt() int { goto RUNRELOADFUNCS } - // Commented out until we need this - //controller.SetConfig(config) - if newConf.LogLevel != "" { configLogLevel := strings.ToLower(strings.TrimSpace(newConf.LogLevel)) switch configLogLevel { diff --git a/internal/cmd/commands/worker/worker.go b/internal/cmd/commands/worker/worker.go index 8d30b16a78..71aeddd639 100644 --- a/internal/cmd/commands/worker/worker.go +++ b/internal/cmd/commands/worker/worker.go @@ -9,9 +9,10 @@ import ( "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/internal/wrapper" "github.com/hashicorp/go-hclog" + wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-multierror" - "github.com/hashicorp/shared-secure-libs/configutil" "github.com/hashicorp/vault/sdk/helper/mlock" "github.com/mitchellh/cli" "github.com/posener/complete" @@ -33,9 +34,10 @@ type Command struct { Config *config.Config worker *worker.Worker - configKMS *configutil.KMS + configWrapper wrapping.Wrapper flagConfig string + flagConfigKms string flagLogLevel string flagLogFormat string flagDev bool @@ -76,6 +78,16 @@ func (c *Command) Flags() *base.FlagSets { 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, @@ -135,6 +147,14 @@ func (c *Command) Run(args []string) int { 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 @@ -206,19 +226,20 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { return 1 } - // preload the KMS for encrypting/decrypting the config parameters - kmss, err := configutil.LoadConfigKMSes(c.flagConfig) + wrapperPath := c.flagConfig + if c.flagConfigKms != "" { + wrapperPath = c.flagConfigKms + } + wrapper, err := wrapper.GetWrapper(wrapperPath, "config") if err != nil { - c.UI.Error("error loading KMS config: " + err.Error()) + c.UI.Error(err.Error()) return 1 } - - for _, kms := range kmss { - for _, purpose := range kms.Purpose { - if purpose == "config" { - c.configKMS = kms - break - } + 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 } } @@ -228,7 +249,7 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { c.UI.Error("Must supply a config file with -config") return 1 } - c.Config, err = config.LoadFile(c.flagConfig, c.configKMS) + c.Config, err = config.LoadFile(c.flagConfig, wrapper) if err != nil { c.UI.Error("Error parsing config: " + err.Error()) return 1 @@ -238,7 +259,7 @@ func (c *Command) ParseFlagsAndConfig(args []string) int { if len(c.flagConfig) == 0 { c.Config, err = config.DevWorker() } else { - c.Config, err = config.LoadFile(c.flagConfig, c.configKMS) + c.Config, err = config.LoadFile(c.flagConfig, wrapper) } if err != nil { c.UI.Error(fmt.Errorf("Error creating dev config: %s", err).Error()) @@ -311,7 +332,7 @@ func (c *Command) WaitForInterrupt() int { goto RUNRELOADFUNCS } - newConf, err = config.LoadFile(c.flagConfig, c.configKMS) + 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 @@ -323,9 +344,6 @@ func (c *Command) WaitForInterrupt() int { goto RUNRELOADFUNCS } - // Commented out until we need this - //c.worker.SetConfig(config) - if newConf.LogLevel != "" { configLogLevel := strings.ToLower(strings.TrimSpace(newConf.LogLevel)) switch configLogLevel { diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index 9dac034f51..46028ef8f8 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -2,13 +2,13 @@ package config import ( "bytes" - "context" "crypto/rand" "encoding/base64" "fmt" "io" "io/ioutil" + wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/hcl" "github.com/hashicorp/shared-secure-libs/configutil" ) @@ -175,7 +175,7 @@ func New() *Config { } // LoadFile loads the configuration from the given file. -func LoadFile(path string, kms *configutil.KMS) (*Config, error) { +func LoadFile(path string, wrapper wrapping.Wrapper) (*Config, error) { d, err := ioutil.ReadFile(path) if err != nil { return nil, err @@ -183,8 +183,8 @@ func LoadFile(path string, kms *configutil.KMS) (*Config, error) { raw := string(d) - if kms != nil { - raw, err = configDecrypt(raw, kms) + if wrapper != nil { + raw, err = configutil.EncryptDecrypt(raw, true, true, wrapper) if err != nil { return nil, err } @@ -235,15 +235,3 @@ func (c *Config) Sanitized() map[string]interface{} { return result } - -func configDecrypt(raw string, kms *configutil.KMS) (string, error) { - wrapper, err := configutil.ConfigureWrapper(kms, nil, nil, nil) - if err != nil { - return raw, err - } - - wrapper.Init(context.Background()) - defer wrapper.Finalize(context.Background()) - - return configutil.EncryptDecrypt(raw, true, true, wrapper) -} diff --git a/internal/wrapper/wrapper.go b/internal/wrapper/wrapper.go new file mode 100644 index 0000000000..747544cbe3 --- /dev/null +++ b/internal/wrapper/wrapper.go @@ -0,0 +1,36 @@ +package wrapper + +import ( + "fmt" + + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/shared-secure-libs/configutil" + "github.com/hashicorp/vault/sdk/helper/strutil" +) + +func GetWrapper(path, purpose string) (wrapping.Wrapper, error) { + kmses, err := configutil.LoadConfigKMSes(path) + if err != nil { + return nil, fmt.Errorf("Error parsing config file: %w", err) + } + + var kms *configutil.KMS + for _, v := range kmses { + if strutil.StrListContains(v.Purpose, purpose) { + if kms != nil { + return nil, fmt.Errorf("Only one %q block marked for %q purpose is allowed", "kms", purpose) + } + kms = v + } + } + if kms == nil { + return nil, nil + } + + wrapper, err := configutil.ConfigureWrapper(kms, nil, nil, nil) + if err != nil { + return nil, fmt.Errorf("Error configuring kms: %w", err) + } + + return wrapper, nil +} diff --git a/recovery/recovery.go b/recovery/recovery.go index 54a019af56..0d0df976a3 100644 --- a/recovery/recovery.go +++ b/recovery/recovery.go @@ -11,8 +11,6 @@ import ( wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/hashicorp/go-uuid" - "github.com/hashicorp/shared-secure-libs/configutil" - "github.com/hashicorp/vault/sdk/helper/strutil" "google.golang.org/protobuf/proto" ) @@ -30,33 +28,6 @@ type Info struct { CreationTime time.Time `json:"creation_time"` } -func GetWrapper(ctx context.Context, path string) (wrapping.Wrapper, error) { - kmses, err := configutil.LoadConfigKMSes(path) - if err != nil { - return nil, fmt.Errorf("Error parsing config file: %w", err) - } - - var kms *configutil.KMS - for _, v := range kmses { - if strutil.StrListContains(v.Purpose, "recovery") { - if kms != nil { - return nil, fmt.Errorf("Only one %q block marked for %q purpose is allowed", "kms", "recovery") - } - kms = v - } - } - if kms == nil { - return nil, fmt.Errorf("No %q block marked for %q purpose is allowed", "kms", "recovery") - } - - wrapper, err := configutil.ConfigureWrapper(kms, nil, nil, nil) - if err != nil { - return nil, fmt.Errorf("Error configuring kms: %w", err) - } - - return wrapper, nil -} - func GenerateRecoveryToken(ctx context.Context, wrapper wrapping.Wrapper) (string, error) { b, err := uuid.GenerateRandomBytes(nonceLength) if err != nil {