2026-04-06 15:48:37 +05:30
// Copyright IBM Corp. 2020, 2026
2023-06-11 20:40:47 -04:00
// SPDX-License-Identifier: BUSL-1.1
2024-05-22 11:11:48 -07:00
package cache
2023-06-11 20:40:47 -04:00
import (
"context"
2023-11-13 13:36:28 -08:00
"errors"
"fmt"
2023-06-11 20:40:47 -04:00
"os"
"strings"
"github.com/hashicorp/boundary/api"
2023-11-13 13:36:28 -08:00
"github.com/hashicorp/boundary/internal/clientcache/internal/client"
2023-06-11 20:40:47 -04:00
"github.com/hashicorp/boundary/internal/clientcache/internal/daemon"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
var (
_ cli . Command = ( * AddTokenCommand )( nil )
_ cli . CommandAutocomplete = ( * AddTokenCommand )( nil )
)
type AddTokenCommand struct {
* base . Command
}
func ( c * AddTokenCommand ) Synopsis () string {
2026-10-12 10:20:06 -07:00
return "Add an auth token to a running threshold cache"
2023-06-11 20:40:47 -04:00
}
func ( c * AddTokenCommand ) Help () string {
helpText := `
2026-10-12 10:20:06 -07:00
Usage: threshold cache add-token [options]
2023-06-11 20:40:47 -04:00
2024-05-22 11:11:48 -07:00
Add an auth token to the cache:
2023-06-11 20:40:47 -04:00
2026-10-12 10:20:06 -07:00
$ threshold cache add-token
2023-06-11 20:40:47 -04:00
For a full list of examples, please see the documentation.
` + c . Flags (). Help ()
return strings . TrimSpace ( helpText )
}
func ( c * AddTokenCommand ) Flags () * base . FlagSets {
2024-01-26 15:56:41 -08:00
set := c . FlagSet ( base . FlagSetOutputFormat )
f := set . NewFlagSet ( "Connection Options" )
f . StringVar ( & base . StringVar {
Name : base . FlagNameAddr ,
Target : & c . FlagAddr ,
EnvVar : api . EnvBoundaryAddr ,
Completion : complete . PredictAnything ,
2026-10-12 10:20:06 -07:00
Usage : "Addr of the Threshold controller, as a complete URL (e.g. https://threshold.example.com:9200)." ,
2024-01-26 15:56:41 -08:00
})
f = set . NewFlagSet ( "Client Options" )
f . StringVar ( & base . StringVar {
Name : "token-name" ,
Target : & c . FlagTokenName ,
EnvVar : base . EnvTokenName ,
Usage : `If specified, the given value will be used as the name when storing the token in the system credential store. This can allow switching user identities for different commands.` ,
})
f . StringVar ( & base . StringVar {
Name : "keyring-type" ,
Target : & c . FlagKeyringType ,
Default : "auto" ,
EnvVar : base . EnvKeyringType ,
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 ( & base . StringVar {
Name : "token" ,
Target : & c . FlagToken ,
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 . BoolVar ( & base . BoolVar {
Name : "output-curl-string" ,
Target : & c . FlagOutputCurlString ,
Usage : "Instead of executing the request, print an equivalent cURL command string and exit." ,
})
2023-06-11 20:40:47 -04:00
return set
}
func ( c * AddTokenCommand ) AutocompleteArgs () complete . Predictor {
return complete . PredictNothing
}
func ( c * AddTokenCommand ) AutocompleteFlags () complete . Flags {
return c . Flags (). Completions ()
}
func ( c * AddTokenCommand ) Run ( args [] string ) int {
ctx := c . Context
f := c . Flags ()
if err := f . Parse ( args ); err != nil {
c . PrintCliError ( err )
return base . CommandUserError
}
2023-11-01 12:45:35 -07:00
client , err := c . Client ()
if err != nil {
c . PrintCliError ( err )
return base . CommandCliError
}
2024-02-06 11:45:53 -08:00
// We ignore the error here since not having a keyring on the platform
// returns an error but can be treated as the keyring type being set to
// none.
keyringType , tokenName , _ := c . DiscoverKeyringTokenInfo ()
2023-06-11 20:40:47 -04:00
2024-02-06 11:45:53 -08:00
resp , apiErr , err := c . Add ( ctx , c . UI , client , keyringType , tokenName )
2023-06-11 20:40:47 -04:00
if err != nil {
c . PrintCliError ( err )
return base . CommandCliError
}
if apiErr != nil {
2024-05-22 11:11:48 -07:00
c . PrintApiError ( apiErr , "Error from cache when adding a token" )
2023-06-11 20:40:47 -04:00
return base . CommandApiError
2023-11-13 13:36:28 -08:00
}
switch base . Format ( c . UI ) {
case "json" :
if ok := c . PrintJsonItem ( resp ); ! ok {
return base . CommandCliError
}
case "table" :
2024-05-22 11:11:48 -07:00
c . UI . Output ( "The cache add-token operation completed successfully." )
2023-06-11 20:40:47 -04:00
}
return base . CommandSuccess
}
2024-02-06 11:45:53 -08:00
// Add builds the UpsertTokenRequest using the client's address and token,
2024-05-22 11:11:48 -07:00
// and trying to leverage the keyring. It then sends the request to the cache.
2024-02-06 11:45:53 -08:00
// The passed in cli.Ui is used to print out any errors when looking up the
// auth token from the keyring. This allows background operations calling this
// method to pass in a silent UI to suppress any output.
func ( c * AddTokenCommand ) Add ( ctx context . Context , ui cli . Ui , apiClient * api . Client , keyringType , tokenName string ) ( * api . Response , * api . Error , error ) {
2023-06-11 20:40:47 -04:00
pa := daemon . UpsertTokenRequest {
2023-11-13 13:36:28 -08:00
BoundaryAddr : apiClient . Addr (),
2023-06-11 20:40:47 -04:00
}
2024-02-06 11:45:53 -08:00
token := apiClient . Token ()
if token == "" {
return nil , nil , errors . New ( "The client auth token is empty." )
}
if parts := strings . SplitN ( token , "_" , 4 ); len ( parts ) == 3 {
pa . AuthTokenId = strings . Join ( parts [: 2 ], "_" )
} else {
return nil , nil , errors . New ( "The client provided auth token is not in the proper format." )
2023-06-11 20:40:47 -04:00
}
2023-11-15 14:40:05 -08:00
var opts [] client . Option
if c . FlagOutputCurlString {
opts = append ( opts , client . WithOutputCurlString ())
2024-02-06 11:45:53 -08:00
pa . AuthToken = "/*token*/"
} else {
pa . AuthToken = token
}
switch keyringType {
case "" , base . NoneKeyring :
// we don't need to do anything else in this situation since the keyring
// doesn't play a part in the request.
default :
// Just because the keyring type is set doesn't mean the token to add
// is contained in it. For example, if the token was intercepted
// from an authentication request with '-format json' then token is
// not stored in the keyring, even if a keyring is provided.
// Try to read the token from the keyring in a best effort way. Ignore
// any errors since the keyring may not be present on the system.
2024-12-23 09:21:18 -08:00
at , _ := base . ReadTokenFromKeyring ( ui , keyringType , tokenName )
2024-02-06 11:45:53 -08:00
if at != nil && ( token == "" || pa . AuthTokenId == at . Id ) {
pa . Keyring = & daemon . KeyringToken {
KeyringType : keyringType ,
TokenName : tokenName ,
}
// since the auth token is stored in the keyring, we can share it
// through the keyring instead of passing it through the request.
pa . AuthToken = ""
}
2023-11-15 14:40:05 -08:00
}
2023-06-11 20:40:47 -04:00
dotPath , err := DefaultDotDirectory ( ctx )
if err != nil {
2023-11-13 13:36:28 -08:00
return nil , nil , err
2023-06-11 20:40:47 -04:00
}
2023-11-15 14:40:05 -08:00
return addToken ( ctx , dotPath , & pa , opts ... )
2023-06-11 20:40:47 -04:00
}
2023-11-15 14:40:05 -08:00
func addToken ( ctx context . Context , daemonPath string , p * daemon . UpsertTokenRequest , opt ... client . Option ) ( * api . Response , * api . Error , error ) {
2024-01-02 11:15:45 -08:00
addr := daemon . SocketAddress ( daemonPath )
_ , err := os . Stat ( addr . Path )
2023-11-13 13:36:28 -08:00
if addr . Scheme == "unix" && err != nil {
return nil , nil , fmt . Errorf ( "Error when detecting if the domain socket is present: %w." , err )
2023-06-11 20:40:47 -04:00
}
2023-11-13 13:36:28 -08:00
c , err := client . New ( ctx , addr )
2023-06-11 20:40:47 -04:00
if err != nil {
2023-11-13 13:36:28 -08:00
return nil , nil , fmt . Errorf ( "Error when making a new client: %w." , err )
2023-06-11 20:40:47 -04:00
}
2023-11-15 14:40:05 -08:00
resp , err := c . Post ( ctx , "/v1/tokens" , p , opt ... )
2023-06-11 20:40:47 -04:00
if err != nil {
2024-05-22 11:11:48 -07:00
return nil , nil , fmt . Errorf ( "Error when sending request to the cache: %w." , err )
2023-06-11 20:40:47 -04:00
}
2023-11-13 13:36:28 -08:00
apiErr , err := resp . Decode ( nil )
return resp , apiErr , err
2023-06-11 20:40:47 -04:00
}