2020-07-14 14:32:47 -07:00
package authenticate
import (
2020-09-26 14:01:49 -04:00
"encoding/base64"
2020-07-22 15:02:58 -04:00
"encoding/json"
2020-07-14 14:32:47 -07:00
"fmt"
"os"
"strings"
"time"
2020-09-24 14:34:58 -07:00
"github.com/hashicorp/boundary/api"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/api/authmethods"
2020-10-01 01:49:20 -04:00
"github.com/hashicorp/boundary/api/authtokens"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/cmd/base"
2020-07-14 14:32:47 -07:00
"github.com/hashicorp/vault/sdk/helper/password"
"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 )
2020-08-13 18:32:58 -04:00
var envPassword = "BOUNDARY_AUTHENTICATE_PASSWORD_PASSWORD"
var envLoginName = "BOUNDARY_AUTHENTICATE_PASSWORD_LOGIN_NAME"
var envAuthMethodId = "BOUNDARY_AUTHENTICATE_AUTH_METHOD_ID"
2020-07-14 14:32:47 -07:00
type PasswordCommand struct {
* base . Command
2020-08-13 18:32:58 -04:00
flagLoginName string
flagPassword string
flagAuthMethodId string
2020-07-14 14:32:47 -07:00
}
func ( c * PasswordCommand ) Synopsis () string {
2020-08-10 17:15:02 -04:00
return wordwrap . WrapString ( "Invoke the password auth method to authenticate with Boundary" , base . TermWidth )
2020-07-14 14:32:47 -07:00
}
func ( c * PasswordCommand ) Help () string {
return base . WrapForHelpText ([] string {
2020-08-10 17:15:02 -04:00
"Usage: boundary authenticate password [options] [args]" ,
2020-07-14 14:32:47 -07:00
"" ,
2020-08-10 17:15:02 -04:00
" Invoke the password auth method to authenticate the Boundary CLI:" ,
2020-07-14 14:32:47 -07:00
"" ,
2020-08-25 00:21:08 -04:00
` $ boundary authenticate password -auth-method-id ampw_1234567890 -login-name foo -password "bar"` ,
2020-07-14 14:32:47 -07:00
}) + 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 {
2020-08-13 18:32:58 -04:00
Name : "login-name" ,
Target : & c . flagLoginName ,
EnvVar : envLoginName ,
2020-09-08 13:27:51 -07:00
Usage : "The login name corresponding to an account within the given auth method" ,
2020-07-14 14:32:47 -07:00
})
f . StringVar ( & base . StringVar {
Name : "password" ,
Target : & c . flagPassword ,
EnvVar : envPassword ,
2020-09-08 13:27:51 -07:00
Usage : "The password associated with the login name" ,
2020-07-14 14:32:47 -07:00
})
f . StringVar ( & base . StringVar {
2020-08-13 18:32:58 -04:00
Name : "auth-method-id" ,
Target : & c . flagAuthMethodId ,
EnvVar : envAuthMethodId ,
Usage : "Specifies the ID of the auth method against which to authenticate" ,
2020-07-14 14:32:47 -07:00
})
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 {
2020-08-13 18:32:58 -04:00
case c . flagLoginName == "" :
c . UI . Error ( "Login name must be provided via -login-name" )
2020-07-14 14:32:47 -07:00
return 1
2020-08-13 18:32:58 -04:00
case c . flagAuthMethodId == "" :
c . UI . Error ( "Auth method ID must be provided via -auth-method-id" )
2020-07-14 14:32:47 -07:00
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 )
}
2020-08-25 00:20:50 -04:00
client , err := c . Client ( base . WithNoTokenScope (), base . WithNoTokenValue ())
2020-07-14 14:32:47 -07:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error creating API client: %s" , err . Error ()))
return 2
}
// 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
2020-09-24 14:34:58 -07:00
result , err := authmethods . NewClient ( client ). Authenticate ( c . Context , c . flagAuthMethodId ,
2020-09-08 13:27:51 -07:00
map [ string ] interface {}{
"login_name" : c . flagLoginName ,
"password" : c . flagPassword ,
})
2020-07-14 14:32:47 -07:00
if err != nil {
2020-09-24 14:34:58 -07:00
if api . AsServerError ( err ) != nil {
c . UI . Error ( fmt . Sprintf ( "Error from controller when performing authentication: %s" , err . Error ()))
return 1
}
2020-07-14 14:32:47 -07:00
c . UI . Error ( fmt . Sprintf ( "Error trying to perform authentication: %s" , err . Error ()))
return 2
}
2020-10-01 01:49:20 -04:00
token := result . GetItem ().( * authtokens . AuthToken )
2020-07-14 14:32:47 -07:00
switch base . Format ( c . UI ) {
case "table" :
c . UI . Output ( base . WrapForHelpText ([] string {
"" ,
"Authentication information:" ,
2020-10-01 00:27:13 -04:00
fmt . Sprintf ( " Account ID: %s" , token . AccountId ),
fmt . Sprintf ( " Auth Method ID: %s" , token . AuthMethodId ),
2020-09-23 03:01:39 -04:00
fmt . Sprintf ( " Expiration Time: %s" , token . ExpirationTime . Local (). Format ( time . RFC1123 )),
2020-09-15 01:08:21 -04:00
fmt . Sprintf ( " Token: %s" , token . Token ),
fmt . Sprintf ( " User ID: %s" , token . UserId ),
2020-07-14 14:32:47 -07:00
}))
2020-09-22 19:50:40 +08:00
case "json" :
2020-10-01 01:49:20 -04:00
jsonOut , err := base . JsonFormatter {}. Format ( token )
2020-09-22 19:50:40 +08:00
if err != nil {
c . UI . Error ( fmt . Errorf ( "Error formatting as JSON: %w" , err ). Error ())
return 1
}
c . UI . Output ( string ( jsonOut ))
2020-07-14 14:32:47 -07:00
}
tokenName := "default"
if c . Command . FlagTokenName != "" {
tokenName = c . Command . FlagTokenName
}
if tokenName != "none" {
2020-09-15 02:07:10 -04:00
marshaled , err := json . Marshal ( token )
2020-07-22 15:02:58 -04:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error marshaling auth token to save to system credential store: %s" , err ))
return 1
}
2020-09-26 14:01:49 -04:00
if err := keyring . Set ( "HashiCorp Boundary Auth Token" , tokenName , base64 . RawStdEncoding . EncodeToString ( marshaled )); err != nil {
2020-07-14 14:32:47 -07:00
c . UI . Error ( fmt . Sprintf ( "Error saving auth token to system credential store: %s" , err ))
return 1
}
}
return 0
}