204 lines
6.0 KiB
Go
204 lines
6.0 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package authenticate
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/boundary/api"
|
|
"github.com/hashicorp/boundary/api/authmethods"
|
|
"github.com/hashicorp/boundary/globals"
|
|
"github.com/hashicorp/boundary/internal/cmd/base"
|
|
"github.com/hashicorp/boundary/internal/cmd/common"
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
"github.com/hashicorp/go-secure-stdlib/password"
|
|
"github.com/mitchellh/cli"
|
|
"github.com/mitchellh/go-wordwrap"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
var (
|
|
_ cli.Command = (*PasswordCommand)(nil)
|
|
_ cli.CommandAutocomplete = (*PasswordCommand)(nil)
|
|
)
|
|
|
|
var (
|
|
envPassword = "BOUNDARY_AUTHENTICATE_PASSWORD_PASSWORD"
|
|
envLoginName = "BOUNDARY_AUTHENTICATE_PASSWORD_LOGIN_NAME"
|
|
)
|
|
|
|
type PasswordCommand struct {
|
|
*base.Command
|
|
|
|
flagLoginName string
|
|
flagPassword string
|
|
|
|
Opts []common.Option
|
|
parsedOpts *common.Options
|
|
}
|
|
|
|
func (c *PasswordCommand) Synopsis() string {
|
|
return wordwrap.WrapString("Invoke the password auth method to authenticate with Boundary", base.TermWidth)
|
|
}
|
|
|
|
func (c *PasswordCommand) Help() string {
|
|
return base.WrapForHelpText([]string{
|
|
"Usage: boundary authenticate password [options] [args]",
|
|
"",
|
|
" Invoke the password auth method to authenticate the Boundary CLI. Example:",
|
|
"",
|
|
` $ boundary authenticate password -auth-method-id ampw_1234567890 -login-name foo`,
|
|
"",
|
|
"",
|
|
}) + c.Flags().Help()
|
|
}
|
|
|
|
func (c *PasswordCommand) Flags() *base.FlagSets {
|
|
set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat)
|
|
f := set.NewFlagSet("Command Options")
|
|
|
|
f.StringVar(&base.StringVar{
|
|
Name: "login-name",
|
|
Target: &c.flagLoginName,
|
|
EnvVar: envLoginName,
|
|
Usage: "The login name corresponding to an account within the given auth method.",
|
|
})
|
|
|
|
f.StringVar(&base.StringVar{
|
|
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 or an env var (env://) from which the password will be read.",
|
|
})
|
|
|
|
f.StringVar(&base.StringVar{
|
|
Name: "auth-method-id",
|
|
EnvVar: "BOUNDARY_AUTH_METHOD_ID",
|
|
Target: &c.FlagAuthMethodId,
|
|
Usage: "The auth-method resource to use for the operation.",
|
|
})
|
|
|
|
if c.parsedOpts == nil || !c.parsedOpts.WithSkipScopeIdFlag {
|
|
f.StringVar(&base.StringVar{
|
|
Name: "scope-id",
|
|
EnvVar: "BOUNDARY_SCOPE_ID",
|
|
Target: &c.FlagScopeId,
|
|
Usage: "The scope ID to use for the operation.",
|
|
})
|
|
}
|
|
|
|
return set
|
|
}
|
|
|
|
func (c *PasswordCommand) AutocompleteArgs() complete.Predictor {
|
|
return complete.PredictAnything
|
|
}
|
|
|
|
func (c *PasswordCommand) AutocompleteFlags() complete.Flags {
|
|
return c.Flags().Completions()
|
|
}
|
|
|
|
func (c *PasswordCommand) Run(args []string) int {
|
|
opts, err := common.GetOpts(c.Opts...)
|
|
if err != nil {
|
|
c.PrintCliError(err)
|
|
return base.CommandCliError
|
|
}
|
|
c.parsedOpts = opts
|
|
|
|
f := c.Flags()
|
|
|
|
if err := f.Parse(args); err != nil {
|
|
c.PrintCliError(err)
|
|
return base.CommandUserError
|
|
}
|
|
|
|
client, err := c.Client(base.WithNoTokenScope(), base.WithNoTokenValue())
|
|
if c.WrapperCleanupFunc != nil {
|
|
defer func() {
|
|
if err := c.WrapperCleanupFunc(); err != nil {
|
|
c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err))
|
|
}
|
|
}()
|
|
}
|
|
if err != nil {
|
|
c.PrintCliError(fmt.Errorf("Error creating API client: %w", err))
|
|
return base.CommandCliError
|
|
}
|
|
|
|
aClient := authmethods.NewClient(client)
|
|
|
|
// if auth method ID isn't passed on the CLI, try looking up the primary auth method ID
|
|
if c.FlagAuthMethodId == "" {
|
|
// if flag for scope is empty try looking up global
|
|
if c.FlagScopeId == "" {
|
|
c.FlagScopeId = "global"
|
|
}
|
|
|
|
pri, err := getPrimaryAuthMethodId(c.Context, aClient, c.FlagScopeId, globals.PasswordAuthMethodPrefix)
|
|
if err != nil {
|
|
c.PrintCliError(err)
|
|
return base.CommandUserError
|
|
}
|
|
|
|
c.FlagAuthMethodId = pri
|
|
}
|
|
|
|
switch c.flagLoginName {
|
|
case "":
|
|
fmt.Print("Please enter the login name (it will be hidden): ")
|
|
value, err := password.Read(os.Stdin)
|
|
fmt.Print("\n")
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the login name. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
|
return base.CommandUserError
|
|
}
|
|
c.flagLoginName = strings.TrimSpace(value)
|
|
}
|
|
|
|
switch c.flagPassword {
|
|
case "":
|
|
fmt.Print("Please enter the password (it will be hidden): ")
|
|
value, err := password.Read(os.Stdin)
|
|
fmt.Print("\n")
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the password. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
|
return base.CommandUserError
|
|
}
|
|
c.flagPassword = strings.TrimSpace(value)
|
|
|
|
default:
|
|
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
|
|
}
|
|
c.flagPassword = password
|
|
}
|
|
|
|
result, err := aClient.Authenticate(c.Context, c.FlagAuthMethodId, "login",
|
|
map[string]any{
|
|
"login_name": c.flagLoginName,
|
|
"password": c.flagPassword,
|
|
})
|
|
if err != nil {
|
|
if apiErr := api.AsServerError(err); apiErr != nil {
|
|
c.PrintApiError(apiErr, "Error from controller when performing authentication")
|
|
return base.CommandApiError
|
|
}
|
|
c.PrintCliError(fmt.Errorf("Error trying to perform authentication: %w", err))
|
|
return base.CommandCliError
|
|
}
|
|
|
|
return saveAndOrPrintToken(c.Command, result)
|
|
}
|