2021-04-23 12:45:08 -05:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2021-04-23 12:45:08 -05:00
package base
import (
"encoding/base64"
"encoding/json"
2023-09-17 19:35:43 -04:00
"errors"
2021-04-23 12:45:08 -05:00
"fmt"
"os"
"runtime"
"strings"
"github.com/hashicorp/boundary/api/authtokens"
nkeyring "github.com/jefferai/keyring"
2024-02-06 11:45:53 -08:00
"github.com/mitchellh/cli"
2021-04-23 12:45:08 -05:00
zkeyring "github.com/zalando/go-keyring"
)
const (
NoneKeyring = "none"
AutoKeyring = "auto"
WincredKeyring = "wincred"
PassKeyring = "pass"
KeychainKeyring = "keychain"
SecretServiceKeyring = "secret-service"
DefaultTokenName = "default"
LoginCollection = "login"
PassPrefix = "HashiCorp_Boundary"
)
2024-12-23 09:21:18 -08:00
// ErrNoToken is returned when no matching token could be found in the keyring
var ErrNoToken = errors . New ( "no token found" )
2021-04-23 12:45:08 -05:00
func ( c * Command ) DiscoverKeyringTokenInfo () ( string , string , error ) {
2022-03-18 13:52:38 -04:00
// Stops the underlying library from invoking a dbus call that ends up
// freezing some systems
os . Setenv ( "DISABLE_KWALLET" , "1" )
2021-04-23 12:45:08 -05:00
tokenName := DefaultTokenName
if c . FlagTokenName != "" {
tokenName = c . FlagTokenName
}
if tokenName == NoneKeyring {
c . UI . Warn ( `"-token-name=none" is deprecated, please use "-keyring-type=none"` )
c . FlagKeyringType = NoneKeyring
}
if c . FlagKeyringType == NoneKeyring {
return "" , "" , nil
}
// Set so we can look it up later when printing out curl strings
os . Setenv ( EnvTokenName , tokenName )
var foundKeyringType bool
keyringType := c . FlagKeyringType
switch runtime . GOOS {
case "windows" :
switch keyringType {
case AutoKeyring , WincredKeyring , PassKeyring :
foundKeyringType = true
if keyringType == AutoKeyring {
keyringType = WincredKeyring
}
}
case "darwin" :
switch keyringType {
case AutoKeyring , KeychainKeyring , PassKeyring :
foundKeyringType = true
if keyringType == AutoKeyring {
keyringType = KeychainKeyring
}
}
default :
switch keyringType {
case AutoKeyring , SecretServiceKeyring , PassKeyring :
foundKeyringType = true
if keyringType == AutoKeyring {
keyringType = PassKeyring
}
}
}
if ! foundKeyringType {
return "" , "" , fmt . Errorf ( "Given keyring type %q is not valid, or not valid for this platform" , c . FlagKeyringType )
}
var available bool
switch keyringType {
case WincredKeyring , KeychainKeyring :
available = true
case PassKeyring , SecretServiceKeyring :
avail := nkeyring . AvailableBackends ()
for _ , a := range avail {
if keyringType == string ( a ) {
available = true
}
}
}
if ! available {
return "" , "" , fmt . Errorf ( "Keyring type %q is not available on this machine. For help with setting up keyrings, see https://www.boundaryproject.io/docs/api-clients/cli." , keyringType )
}
os . Setenv ( EnvKeyringType , keyringType )
return keyringType , tokenName , nil
}
2024-12-23 09:21:18 -08:00
// ReadTokenFromKeyring will attempt to read the token with the name tokenName from the keyring
// described by keyringType. It will return ErrNoToken if no token is found in the provided keyring.
func ReadTokenFromKeyring ( ui cli . Ui , keyringType , tokenName string ) ( * authtokens . AuthToken , error ) {
const op = "base.ReadTokenFromKeyring"
2021-04-23 12:45:08 -05:00
var token string
var err error
switch keyringType {
case NoneKeyring :
2024-12-23 09:21:18 -08:00
return nil , ErrNoToken
2021-04-23 12:45:08 -05:00
case WincredKeyring , KeychainKeyring :
token , err = zkeyring . Get ( StoredTokenName , tokenName )
if err != nil {
if err == zkeyring . ErrNotFound {
2024-02-06 11:45:53 -08:00
ui . Error ( "No saved credential found, continuing without" )
2021-04-23 12:45:08 -05:00
} else {
2024-02-06 11:45:53 -08:00
ui . Warn ( "Token must be provided via BOUNDARY_TOKEN env var or -token flag. Reading the token can also be disabled via -keyring-type=none." )
2024-12-23 09:21:18 -08:00
return nil , fmt . Errorf ( "%s: error reading auth token from keyring: %w" , op , err )
2021-04-23 12:45:08 -05:00
}
}
default :
krConfig := nkeyring . Config {
LibSecretCollectionName : LoginCollection ,
PassPrefix : PassPrefix ,
AllowedBackends : [] nkeyring . BackendType { nkeyring . BackendType ( keyringType )},
}
kr , err := nkeyring . Open ( krConfig )
if err != nil {
2024-02-06 11:45:53 -08:00
ui . Warn ( "Token must be provided via BOUNDARY_TOKEN env var or -token flag. Reading the token can also be disabled via -keyring-type=none." )
2024-12-23 09:21:18 -08:00
return nil , fmt . Errorf ( "%s: failed to open keyring: %w" , op , err )
2021-04-23 12:45:08 -05:00
}
item , err := kr . Get ( tokenName )
if err != nil {
2024-02-06 11:45:53 -08:00
ui . Warn ( "Token must be provided via BOUNDARY_TOKEN env var or -token flag. Reading the token can also be disabled via -keyring-type=none." )
2024-12-23 09:21:18 -08:00
return nil , fmt . Errorf ( "%s: failed to get token from keyring: %w" , op , err )
2021-04-23 12:45:08 -05:00
}
token = string ( item . Data )
}
if token != "" {
tokenBytes , err := base64 . RawStdEncoding . DecodeString ( token )
switch {
case err != nil :
2024-12-23 09:21:18 -08:00
return nil , fmt . Errorf ( "error base64-unmarshaling stored token from system credential store: %w" , err )
2021-04-23 12:45:08 -05:00
case len ( tokenBytes ) == 0 :
2024-12-23 09:21:18 -08:00
return nil , errors . New ( "zero length token after decoding stored token from system credential store" )
2021-04-23 12:45:08 -05:00
default :
var authToken authtokens . AuthToken
if err := json . Unmarshal ( tokenBytes , & authToken ); err != nil {
2024-12-23 09:21:18 -08:00
return nil , fmt . Errorf ( "error unmarshaling stored token information after reading from system credential store: %s" , err )
2021-04-23 12:45:08 -05:00
} else {
2024-12-23 09:21:18 -08:00
return & authToken , nil
2021-04-23 12:45:08 -05:00
}
}
}
2024-12-23 09:21:18 -08:00
return nil , ErrNoToken
2021-04-23 12:45:08 -05:00
}
2024-12-23 09:21:18 -08:00
// ReadTokenFromKeyring will attempt to read the token with the name tokenName from the keyring
// described by keyringType. It will return ErrNoToken if no token is found in the provided keyring.
func ( c * Command ) ReadTokenFromKeyring ( keyringType , tokenName string ) ( * authtokens . AuthToken , error ) {
2024-02-06 11:45:53 -08:00
return ReadTokenFromKeyring ( c . UI , keyringType , tokenName )
}
2021-04-23 12:45:08 -05:00
func TokenIdFromToken ( token string ) ( string , error ) {
split := strings . Split ( token , "_" )
if len ( split ) < 3 {
return "" , errors . New ( "Unexpected stored token format" )
}
return strings . Join ( split [ 0 : 2 ], "_" ), nil
}