The commands added are: boundary daemon start boundary daemon stop boundary daemon add-token boundary daemon status boundary search If these actions are performed on a platform that doesn't support sqlite natively in go an error is returned. Besides some hooks into the cmd package all the significant logic resides in internal/clientcache feat (cmds/authenticate): output the token name when storing token feat: update go-dbw which now uses pure go sqlite driver refact (base.Command): export FlagOutputCurlString Exporting FlagOutputCurlString allows other commands that embed the base.Command to access the flag as needed feat: add a cache Repository to manage cache storage feat: add a "cache" CLI command This new command will support both managing and searching the local CLI cache daemon. feat: start cache daemon as needed when issue CLI commands Move files to new directories and add persona table (#3526) * Add persona table and move files to new directories Client daemon now uses unix sockets and update pathing (#3535) * Add unix socket listen and dialers with file permissions applied * Update the pathing to match RFC Cache refreshing now uses all stored personas. (#3545) Updating headers for LLB files Decouple daemon and cache server (#3566) * Separate daemon from server * Add persona handler to daemon server (#3579) Moved search to use mql and added tests for the search command (#3589) * search uses hashicorp/mql Windows support for client side daemon (#3618) * exec.Command for background process * Log to stderr when in foreground and file always * Use pid files across platform for start and stop Requests with version info must match the daemon's version (#3640) * requests with version info must match daemons Update dependencies and breakout cgo/pure go sqlite drivers (#3657) upgrade go-dbw deps and add cgo build tags Enable graceful shutdown in windows (#3695) * Enable graceful shutdown in windows Co-authored-by: Jim <jlambert@hashicorp.com> Targets delete when a persona changes address or user id (#3673) * Handle cache refresh in repo Add sessions to the client side cache (#3682) * Add sessions to repository * Search command supports sessions * adding session related tests to db and refresh Change cache schema from personas to tokens+users (#3737) * Change from personas to tokens+users * Separate keyring tokens from auth tokens Add support for keyringless auth tokens (#3765) * Add support for keyringless auth tokens Remove expired auth tokens from cache (#3768) * Remove expired auth tokens from cache Complete the TODO of checking for inflight tx (#3779) Delete tokens that the controller says are invalid (#3782) fix search test with boundary token check (#3799) Collect resource info in repository_<resource>s.go (#3769) * Collect resource info in repository_<resource>s.go * Move search logic into the domain (#3794) Daemon and search restricted to supported platforms (#3801) * Daemon and search restricted to supported platforms Reorganize the clientcache packages (#3814) * Refactor for self contained domain logic * Put clientcache stuff under internal
131 lines
4.6 KiB
Go
131 lines
4.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package authenticate
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/api/authmethods"
|
|
"github.com/hashicorp/boundary/api/authtokens"
|
|
"github.com/hashicorp/boundary/internal/cmd/base"
|
|
nkeyring "github.com/jefferai/keyring"
|
|
zkeyring "github.com/zalando/go-keyring"
|
|
)
|
|
|
|
func saveAndOrPrintToken(c *base.Command, result *authmethods.AuthenticateResult) int {
|
|
token := new(authtokens.AuthToken)
|
|
if err := json.Unmarshal(result.GetRawAttributes(), token); err != nil {
|
|
c.PrintCliError(fmt.Errorf("Error trying to decode response as an auth token: %w", err))
|
|
return base.CommandCliError
|
|
}
|
|
|
|
switch base.Format(c.UI) {
|
|
case "table":
|
|
c.UI.Output(base.WrapForHelpText([]string{
|
|
"",
|
|
"Authentication information:",
|
|
fmt.Sprintf(" Account ID: %s", token.AccountId),
|
|
fmt.Sprintf(" Auth Method ID: %s", token.AuthMethodId),
|
|
fmt.Sprintf(" Expiration Time: %s", token.ExpirationTime.Local().Format(time.RFC1123)),
|
|
fmt.Sprintf(" User ID: %s", token.UserId),
|
|
}))
|
|
|
|
case "json":
|
|
if ok := c.PrintJsonItem(result.GetResponse()); !ok {
|
|
return base.CommandCliError
|
|
}
|
|
return base.CommandSuccess
|
|
}
|
|
|
|
var gotErr bool
|
|
keyringType, tokenName, err := c.DiscoverKeyringTokenInfo()
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error fetching keyring information: %s", err))
|
|
gotErr = true
|
|
} else if keyringType != "none" &&
|
|
tokenName != "none" &&
|
|
keyringType != "" &&
|
|
tokenName != "" {
|
|
marshaled, err := json.Marshal(token)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error marshaling auth token to save to keyring: %s", err))
|
|
gotErr = true
|
|
} else {
|
|
switch keyringType {
|
|
case "wincred", "keychain":
|
|
if err := zkeyring.Set(base.StoredTokenName, tokenName, base64.RawStdEncoding.EncodeToString(marshaled)); err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error saving auth token to %q keyring: %s", keyringType, err))
|
|
gotErr = true
|
|
}
|
|
|
|
default:
|
|
krConfig := nkeyring.Config{
|
|
LibSecretCollectionName: "login",
|
|
PassPrefix: "HashiCorp_Boundary",
|
|
AllowedBackends: []nkeyring.BackendType{nkeyring.BackendType(keyringType)},
|
|
}
|
|
|
|
kr, err := nkeyring.Open(krConfig)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error opening %q keyring: %s", keyringType, err))
|
|
gotErr = true
|
|
break
|
|
}
|
|
|
|
if err := kr.Set(nkeyring.Item{
|
|
Key: tokenName,
|
|
Data: []byte(base64.RawStdEncoding.EncodeToString(marshaled)),
|
|
}); err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error storing token in %q keyring: %s", keyringType, err))
|
|
gotErr = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !gotErr {
|
|
c.UI.Output(fmt.Sprintf("\nThe token name %q was successfully stored in the chosen keyring and is not displayed here.", tokenName))
|
|
}
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case gotErr:
|
|
c.UI.Warn(fmt.Sprintf("The token was not successfully saved to a system keyring. The token is:\n\n%s\n\nIt must be manually passed in via the BOUNDARY_TOKEN env var or -token flag. Storing the token can also be disabled via -keyring-type=none.", token.Token))
|
|
case c.FlagKeyringType == "none":
|
|
c.UI.Warn("\nStoring the token in a keyring was disabled. The token is:")
|
|
c.UI.Output(token.Token)
|
|
c.UI.Warn("Please be sure to store it safely!")
|
|
}
|
|
|
|
return base.CommandSuccess
|
|
}
|
|
|
|
// getPrimaryAuthMethodId iterates over client.List() to find the primary auth method ID for the
|
|
// given scopeId. If scope ID is empty or no primary auth method is found, it returns an error.
|
|
func getPrimaryAuthMethodId(ctx context.Context, client *authmethods.Client, scopeId, amType string) (string, error) {
|
|
if scopeId == "" {
|
|
return "", fmt.Errorf("Must pass a non empty scope ID string to GetPrimaryAuthMethodId()")
|
|
}
|
|
authMethodListResult, err := client.List(ctx, scopeId)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, m := range authMethodListResult.GetItems() {
|
|
if m.IsPrimary {
|
|
if !strings.HasPrefix(m.Id, amType) {
|
|
return "", fmt.Errorf("Error looking up primary auth method in scope '%s': got '%s' but the command requires an auth method prefix of '%s'. Make sure the sub command you're using matches the primary auth method type in the scope being used. For example, if using the password sub command the primary auth method must have a prefix of 'ampw'.\n\nSee 'boundary authenticate -h' for available sub command usage.", scopeId, m.Id, amType)
|
|
}
|
|
|
|
return m.Id, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("Primary auth method not found for scope ID: '%s'. Please set a primary auth method on this scope or pass one explicitly using an authenticate sub command (see 'boundary authenticate -h') along with the -auth-method-id flag.", scopeId)
|
|
}
|