This allows us to use the token's scope to set the default scope for an action in the client instead of relying on env vars.
171 lines
4.7 KiB
Go
171 lines
4.7 KiB
Go
package authenticate
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/helper/password"
|
|
"github.com/hashicorp/watchtower/api/scopes"
|
|
"github.com/hashicorp/watchtower/internal/cmd/base"
|
|
"github.com/kr/pretty"
|
|
"github.com/mitchellh/cli"
|
|
"github.com/mitchellh/go-wordwrap"
|
|
"github.com/posener/complete"
|
|
"github.com/zalando/go-keyring"
|
|
)
|
|
|
|
var _ cli.Command = (*PasswordCommand)(nil)
|
|
var _ cli.CommandAutocomplete = (*PasswordCommand)(nil)
|
|
|
|
var envPassword = "WATCHTOWER_AUTHENTICATE_PASSWORD"
|
|
var envName = "WATCHTOWER_AUTHENTICATE_NAME"
|
|
var envMethodId = "WATCHTOWER_AUTHENTICATE_METHOD_ID"
|
|
|
|
type PasswordCommand struct {
|
|
*base.Command
|
|
|
|
flagName string
|
|
flagPassword string
|
|
flagMethodId string
|
|
}
|
|
|
|
func (c *PasswordCommand) Synopsis() string {
|
|
return wordwrap.WrapString("Invoke the password auth method to authenticate with Watchtower", base.TermWidth)
|
|
}
|
|
|
|
func (c *PasswordCommand) Help() string {
|
|
return base.WrapForHelpText([]string{
|
|
"Usage: watchtower authenticate password [options] [args]",
|
|
"",
|
|
" Invoke the password auth method to authenticate the Watchtower CLI:",
|
|
"",
|
|
" $ watchtower authenticate password -name=foo -password=bar",
|
|
"",
|
|
" If more than one instance of the password auth method exists, use the -method-id flag:",
|
|
"",
|
|
` $ watchtower authenticate password -method-id am_12345 -name foo -password "bar"`,
|
|
}) + 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: "name",
|
|
Target: &c.flagName,
|
|
EnvVar: envName,
|
|
Usage: "Login name",
|
|
})
|
|
|
|
f.StringVar(&base.StringVar{
|
|
Name: "password",
|
|
Target: &c.flagPassword,
|
|
EnvVar: envPassword,
|
|
Usage: "Password",
|
|
})
|
|
|
|
f.StringVar(&base.StringVar{
|
|
Name: "method-id",
|
|
Target: &c.flagMethodId,
|
|
EnvVar: envMethodId,
|
|
Usage: "Specify if more than one instance of a password auth method exists in the given org",
|
|
})
|
|
|
|
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 {
|
|
f := c.Flags()
|
|
|
|
if err := f.Parse(args); err != nil {
|
|
c.UI.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
switch {
|
|
case c.flagName == "":
|
|
c.UI.Error("Name must be provided via -name")
|
|
return 1
|
|
case c.flagMethodId == "":
|
|
c.UI.Error("Auth method ID must be provided via -method-id")
|
|
return 1
|
|
}
|
|
|
|
if c.flagPassword == "" {
|
|
fmt.Print("Password is not set as flag or in env, please enter it now (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 2
|
|
}
|
|
c.flagPassword = strings.TrimSpace(value)
|
|
}
|
|
|
|
client, err := c.Client()
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error creating API client: %s", err.Error()))
|
|
return 2
|
|
}
|
|
|
|
scp := &scopes.Scope{
|
|
Client: client,
|
|
}
|
|
|
|
// note: Authenticate() calls SetToken() under the hood to set the
|
|
// auth bearer on the client so we do not need to do anything with the
|
|
// returned token after this call, so we ignore it
|
|
result, apiErr, err := scp.Authenticate(c.Context, c.flagMethodId, c.flagName, c.flagPassword)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error trying to perform authentication: %s", err.Error()))
|
|
return 2
|
|
}
|
|
if apiErr != nil {
|
|
c.UI.Error(fmt.Sprintf("Error from controller when performing authentication: %s", pretty.Sprint(apiErr)))
|
|
return 1
|
|
}
|
|
|
|
switch base.Format(c.UI) {
|
|
case "table":
|
|
c.UI.Output(base.WrapForHelpText([]string{
|
|
"",
|
|
"Authentication information:",
|
|
fmt.Sprintf(" Token: %s", result.Token),
|
|
fmt.Sprintf(" User ID: %s", result.UserId),
|
|
fmt.Sprintf(" Expiration Time: %s", result.ExpirationTime.Local().Format(time.RFC3339)),
|
|
}))
|
|
}
|
|
|
|
tokenName := "default"
|
|
if c.Command.FlagTokenName != "" {
|
|
tokenName = c.Command.FlagTokenName
|
|
}
|
|
if tokenName != "none" {
|
|
marshaled, err := json.Marshal(result)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error marshaling auth token to save to system credential store: %s", err))
|
|
return 1
|
|
}
|
|
if err := keyring.Set("HashiCorp Watchtower Auth Token", tokenName, base64.RawStdEncoding.EncodeToString(marshaled)); err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error saving auth token to system credential store: %s", err))
|
|
return 1
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|