diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fd98d0ccc..cf83518f1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,10 +37,17 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. * Credential stores: Static-type credential stores/credentials now use typed prefixes for any newly-created resources. Existing resources will not be affected. ([PR](https://github.com/hashicorp/boundary/pull/2256)) -* Removal of `-token` flag from CLI: Passing a token this way can reveal the - token to any user or service that can look at process information. CLI users - can use the keyring integration or `BOUNDARY_TOKEN` env var. - ([PR](https://github.com/hashicorp/boundary/pull/2303)) +* Change of behavior on `-token` flag in CLI: Passing a token this way can + reveal the token to any user or service that can look at process information. + This flag must now reference a file on disk or an env var. Direct usage of the + `BOUNDARY_TOKEN` env var is also deprecated as it can show up in environment + information; the `env://` format now supported by the `-token` flag causes the + Boundary process to read it instead of the shell so is safer. + ([PR](https://github.com/hashicorp/boundary/pull/2327)) +* Change of behavior on `-password` flag in CLI: The same change made above for + `-token` has also been applied to `-password` or, for supporting resource + types, `-current-password` and `-new-password`. + ([PR](https://github.com/hashicorp/boundary/pull/2327)) ## 0.9.1 (2022/07/06) diff --git a/go.mod b/go.mod index 965a9da46a..98c32262c4 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/hashicorp/go-secure-stdlib/kv-builder v0.1.1 github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.4 github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2 + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 github.com/hashicorp/go-secure-stdlib/password v0.1.1 github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.1 github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 diff --git a/go.sum b/go.sum index 76038ae53f..a42fac0b90 100644 --- a/go.sum +++ b/go.sum @@ -713,6 +713,8 @@ github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtf github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2 h1:Tz6v3Jb2DRnDCfifRSjYKG0m8dLdNq6bcDkB41en7nw= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.2/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/password v0.1.1 h1:6JzmBqXprakgFEHwBgdchsjaA9x3GyjdI568bXKxa60= github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.1 h1:s47ax4xEIStj1YV7TRz6zS8LlNrTU86aEhL8pfSOG4U= diff --git a/internal/cmd/base/base.go b/internal/cmd/base/base.go index df6cbdbea0..318b3257ca 100644 --- a/internal/cmd/base/base.go +++ b/internal/cmd/base/base.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping/v2" configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2" + "github.com/hashicorp/go-secure-stdlib/parseutil" "github.com/hashicorp/go-secure-stdlib/pluginutil/v2" "github.com/mitchellh/cli" "github.com/pkg/errors" @@ -93,6 +94,7 @@ type Command struct { flagTLSInsecure bool flagFormat string + FlagToken string FlagTokenName string FlagKeyringType string FlagRecoveryConfig string @@ -273,6 +275,22 @@ func (c *Command) Client(opt ...Option) (*api.Client, error) { c.client.SetRecoveryKmsWrapper(wrapper) + case c.FlagToken != "": + token, err := parseutil.MustParsePath(c.FlagToken) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + return nil, errors.New("Token flag must be used with env:// or file:// syntax") + default: + return nil, fmt.Errorf("error parsing token flag: %w", err) + } + c.client.SetToken(token) + + case c.FlagToken == "" && os.Getenv(envToken) != "": + // Backwards compat: allow reading from existing BOUNDARY_TOKEN env var + c.UI.Warn(`Direct usage of BOUNDARY_TOKEN env var is deprecated; please use "-token env://" format, e.g. "-token env://BOUNDARY_TOKEN" to specify an env var to use.`) + c.client.SetToken(os.Getenv(envToken)) + case c.client.Token() == "" && strings.ToLower(c.FlagKeyringType) != "none": keyringType, tokenName, err := c.DiscoverKeyringTokenInfo() if err != nil { @@ -393,6 +411,13 @@ func (c *Command) FlagSet(bit FlagSetBit) *FlagSets { Usage: `The type of keyring to use. Defaults to "auto" which will use the Windows credential manager, OSX keychain, or cross-platform password store depending on platform. Set to "none" to disable keyring functionality. Available types, depending on platform, are: "wincred", "keychain", "pass", and "secret-service".`, }) + f.StringVar(&StringVar{ + Name: "token", + Target: &c.FlagToken, + EnvVar: envToken, + Usage: `A URL pointing to a file on disk (file://) from which a token will be read or an env var (env://) from which the token will be read. Overrides the "token-name" parameter.`, + }) + f.StringVar(&StringVar{ Name: "recovery-config", Target: &c.FlagRecoveryConfig, diff --git a/internal/cmd/commands/accountscmd/funcs.go b/internal/cmd/commands/accountscmd/funcs.go index 5cefc5b517..e74a323368 100644 --- a/internal/cmd/commands/accountscmd/funcs.go +++ b/internal/cmd/commands/accountscmd/funcs.go @@ -1,6 +1,7 @@ package accountscmd import ( + "errors" "fmt" "os" "strings" @@ -141,8 +142,13 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]accounts.O c.flagPassword = strings.TrimSpace(value) default: - password, err := parseutil.ParsePath(c.flagPassword) - if err != nil && err.Error() != parseutil.ErrNotAUrl.Error() { + password, err := parseutil.MustParsePath(c.flagPassword) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + c.UI.Error("Password flag must be used with env:// or file:// syntax or left empty for an interactive prompt") + return false + default: c.UI.Error(fmt.Sprintf("Error parsing password flag: %v", err)) return false } @@ -164,8 +170,13 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]accounts.O c.flagCurrentPassword = strings.TrimSpace(value) default: - password, err := parseutil.ParsePath(c.flagCurrentPassword) - if err != nil && err.Error() != parseutil.ErrNotAUrl.Error() { + password, err := parseutil.MustParsePath(c.flagCurrentPassword) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + c.UI.Error("Current password flag must be used with env:// or file:// syntax or left empty for an interactive prompt") + return false + default: c.UI.Error(fmt.Sprintf("Error parsing current password flag: %v", err)) return false } @@ -196,8 +207,13 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]accounts.O c.flagNewPassword = strings.TrimSpace(value) default: - password, err := parseutil.ParsePath(c.flagNewPassword) - if err != nil && err.Error() != parseutil.ErrNotAUrl.Error() { + password, err := parseutil.MustParsePath(c.flagNewPassword) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + c.UI.Error("New password flag must be used with env:// or file:// syntax or left empty for an interactive prompt") + return false + default: c.UI.Error(fmt.Sprintf("Error parsing new password flag: %v", err)) return false } diff --git a/internal/cmd/commands/accountscmd/password_funcs.go b/internal/cmd/commands/accountscmd/password_funcs.go index 617614cb20..c50319fbb7 100644 --- a/internal/cmd/commands/accountscmd/password_funcs.go +++ b/internal/cmd/commands/accountscmd/password_funcs.go @@ -1,6 +1,7 @@ package accountscmd import ( + "errors" "fmt" "os" "strings" @@ -73,7 +74,7 @@ func extraPasswordFlagsFuncImpl(c *PasswordCommand, set *base.FlagSets, f *base. f.StringVar(&base.StringVar{ Name: "password", Target: &c.flagPassword, - Usage: "The password for the account. If blank, the command will prompt for the password to be entered interactively in a non-echoing way. Otherwise, this can refer to a file on disk (file://) from which a password will be read; an env var (env://) from which the password will be read; or a string containing the password.", + Usage: "The password for the account. If blank, the command will prompt for the password to be entered interactively in a non-echoing way. Otherwise, this can refer to a file on disk (file://) from which a password will be read or an env var (env://) from which the password will be read.", }) } } @@ -117,8 +118,13 @@ func extraPasswordFlagsHandlingFuncImpl(c *PasswordCommand, _ *base.FlagSets, op *opts = append(*opts, accounts.WithPasswordAccountPassword(strings.TrimSpace(value))) default: - password, err := parseutil.ParsePath(c.flagPassword) - if err != nil && err.Error() != parseutil.ErrNotAUrl.Error() { + password, err := parseutil.MustParsePath(c.flagPassword) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + c.UI.Error("Password flag must be used with env:// or file:// syntax or left empty for an interactive prompt") + return false + default: c.UI.Error(fmt.Sprintf("Error parsing password flag: %v", err)) return false } diff --git a/internal/cmd/commands/authenticate/password.go b/internal/cmd/commands/authenticate/password.go index f9f91414fa..eb0abf03c7 100644 --- a/internal/cmd/commands/authenticate/password.go +++ b/internal/cmd/commands/authenticate/password.go @@ -64,7 +64,7 @@ func (c *PasswordCommand) Flags() *base.FlagSets { Name: "password", Target: &c.flagPassword, EnvVar: envPassword, - Usage: "The password associated with the login name. If blank, the command will prompt for the password to be entered interactively in a non-echoing way. Otherwise, this can refer to a file on disk (file://) from which a password will be read; an env var (env://) from which the password will be read; or a string containing the password.", + Usage: "The password associated with the login name. If blank, the command will prompt for the password to be entered interactively in a non-echoing way. Otherwise, this can refer to a file on disk (file://) from which a password will be read or an env var (env://) from which the password will be read.", }) f.StringVar(&base.StringVar{ @@ -114,8 +114,13 @@ func (c *PasswordCommand) Run(args []string) int { c.flagPassword = strings.TrimSpace(value) default: - password, err := parseutil.ParsePath(c.flagPassword) - if err != nil && err.Error() != parseutil.ErrNotAUrl.Error() { + password, err := parseutil.MustParsePath(c.flagPassword) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + c.UI.Error("Password flag must be used with env:// or file:// syntax or left empty for an interactive prompt") + return base.CommandUserError + default: c.UI.Error(fmt.Sprintf("Error parsing password flag: %v", err)) return base.CommandUserError } diff --git a/internal/cmd/commands/credentialscmd/ssh-private-key_funcs.go b/internal/cmd/commands/credentialscmd/ssh-private-key_funcs.go index feb6fdb33b..1d77f89ce8 100644 --- a/internal/cmd/commands/credentialscmd/ssh-private-key_funcs.go +++ b/internal/cmd/commands/credentialscmd/ssh-private-key_funcs.go @@ -1,6 +1,9 @@ package credentialscmd import ( + "errors" + "fmt" + "github.com/hashicorp/boundary/api/credentials" "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/go-secure-stdlib/parseutil" @@ -58,9 +61,14 @@ func extraSshPrivateKeyFlagHandlingFuncImpl(c *SshPrivateKeyCommand, _ *base.Fla switch c.flagPrivateKey { case "": default: - privateKey, err := parseutil.ParsePath(c.flagPrivateKey) - if err != nil && err.Error() != parseutil.ErrNotAUrl.Error() { - c.UI.Error("Error parsing private key flag: " + err.Error()) + privateKey, err := parseutil.MustParsePath(c.flagPrivateKey) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + c.UI.Error("Private key flag must be used with env:// or file:// syntax") + return false + default: + c.UI.Error(fmt.Sprintf("Error parsing private key flag: %v", err)) return false } *opts = append(*opts, credentials.WithSshPrivateKeyCredentialPrivateKey(privateKey)) diff --git a/internal/cmd/commands/credentialscmd/username-password_funcs.go b/internal/cmd/commands/credentialscmd/username-password_funcs.go index fe83731f74..885cdc9780 100644 --- a/internal/cmd/commands/credentialscmd/username-password_funcs.go +++ b/internal/cmd/commands/credentialscmd/username-password_funcs.go @@ -1,6 +1,9 @@ package credentialscmd import ( + "errors" + "fmt" + "github.com/hashicorp/boundary/api/credentials" "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/go-secure-stdlib/parseutil" @@ -43,7 +46,7 @@ func extraUsernamePasswordFlagsFuncImpl(c *UsernamePasswordCommand, set *base.Fl f.StringVar(&base.StringVar{ Name: passwordFlagName, Target: &c.flagPassword, - Usage: "The password associated with the credential. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.", + Usage: "The password associated with the credential. This can be a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.", }) } } @@ -58,9 +61,14 @@ func extraUsernamePasswordFlagHandlingFuncImpl(c *UsernamePasswordCommand, _ *ba switch c.flagPassword { case "": default: - password, err := parseutil.ParsePath(c.flagPassword) - if err != nil && err.Error() != parseutil.ErrNotAUrl.Error() { - c.UI.Error("Error parsing password flag: " + err.Error()) + password, err := parseutil.MustParsePath(c.flagPassword) + switch { + case err == nil: + case errors.Is(err, parseutil.ErrNotParsed): + c.UI.Error("Password flag must be used with env:// or file:// syntax") + return false + default: + c.UI.Error(fmt.Sprintf("Error parsing password flag: %v", err)) return false } *opts = append(*opts, credentials.WithUsernamePasswordCredentialPassword(password))