Update target credential library service, API, SDK, CLI (#1343)

This commit is contained in:
Jeff Mitchell authored and GitHub committed 2021-06-25 14:06:31 -04:00
1 parent 83c65d0d4b
commit 86c9a90554
27 files changed
+1474 -1127

No files matched your search

+17 -5
View File
@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/scopes"
"github.com/hashicorp/boundary/api/targets"
"github.com/mitchellh/cli"
"github.com/mitchellh/go-wordwrap"
"github.com/pkg/errors"
@@ -241,18 +242,29 @@ func (c *Command) PrintCliError(err error) {
}
// PrintJsonItem prints the given item to the UI in JSON format
func (c *Command) PrintJsonItem(result api.GenericResult) bool {
func (c *Command) PrintJsonItem(result api.GenericResult, opt ...Option) bool {
resp := result.GetResponse()
if resp == nil {
c.PrintCliError(errors.New("Error formatting as JSON: no response given to item formatter"))
return false
}
if r := resp.HttpResponse(); r != nil {
opt = append(opt, WithStatusCode(r.StatusCode))
}
return c.PrintJson(resp.Body.Bytes(), opt...)
}
// PrintJson prints the given raw JSON in our common format
func (c *Command) PrintJson(input json.RawMessage, opt ...Option) bool {
opts := getOpts(opt...)
output := struct {
StatusCode int `json:"status_code"`
Item json.RawMessage `json:"item,omitempty"`
StatusCode int `json:"status_code,omitempty"`
DecodedCredentials []*targets.SessionCredential `json:"decoded_credentials,omitempty"`
Item json.RawMessage `json:"item,omitempty"`
}{
StatusCode: resp.HttpResponse().StatusCode,
Item: resp.Body.Bytes(),
StatusCode: opts.withStatusCode,
DecodedCredentials: opts.withDecodedCredentials,
Item: input,
}
b, err := JsonFormatter{}.Format(output)
if err != nil {
+18
View File
@@ -1,5 +1,7 @@
package base
import "github.com/hashicorp/boundary/api/targets"
// getOpts - iterate the inbound Options and return a struct.
func getOpts(opt ...Option) Options {
opts := getDefaultOptions()
@@ -25,6 +27,8 @@ type Options struct {
withContainerImage string
withDialect string
withAttributeFieldPrefix string
withStatusCode int
withDecodedCredentials []*targets.SessionCredential
}
func getDefaultOptions() Options {
@@ -120,3 +124,17 @@ func WithAttributeFieldPrefix(p string) Option {
o.withAttributeFieldPrefix = p
}
}
// WithStatusCode allows passing status codes to functions
func WithStatusCode(statusCode int) Option {
return func(o *Options) {
o.withStatusCode = statusCode
}
}
// WithDecodedCredentials allows passing decoded credentials
func WithDecodedCredentials(creds []*targets.SessionCredential) Option {
return func(o *Options) {
o.withDecodedCredentials = creds
}
}
+102 -13
View File
@@ -5,6 +5,7 @@ import (
"crypto/ed25519"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@@ -40,12 +41,13 @@ import (
const sessionCancelTimeout = 10 * time.Second
type SessionInfo struct {
Address string `json:"address"`
Port int `json:"port"`
Protocol string `json:"protocol"`
Expiration time.Time `json:"expiration"`
ConnectionLimit int32 `json:"connection_limit"`
SessionId string `json:"session_id"`
Address string `json:"address"`
Port int `json:"port"`
Protocol string `json:"protocol"`
Expiration time.Time `json:"expiration"`
ConnectionLimit int32 `json:"connection_limit"`
SessionId string `json:"session_id"`
Credentials []*targets.SessionCredential `json:"credentials,omitempty"`
}
type ConnectionInfo struct {
@@ -72,6 +74,7 @@ type Command struct {
flagHostId string
flagExec string
flagUsername string
flagDbname string
// HTTP
httpFlags
@@ -90,6 +93,7 @@ type Command struct {
Func string
sessionAuthz *targets.SessionAuthorization
sessionAuthzData *targetspb.SessionAuthorizationData
connWg *sync.WaitGroup
@@ -103,6 +107,8 @@ type Command struct {
proxyCtx context.Context
proxyCancel context.CancelFunc
outputJsonErrors bool
cleanupFuncs []func() error
}
func (c *Command) Synopsis() string {
@@ -350,9 +356,9 @@ func (c *Command) Run(args []string) (retCode int) {
if authzString[0] == '{' {
// Attempt to decode the JSON output of an authorize-session call
// and pull the token out of there
sessionAuthz := new(targets.SessionAuthorization)
if err := json.Unmarshal([]byte(authzString), sessionAuthz); err == nil {
authzString = sessionAuthz.AuthorizationToken
c.sessionAuthz = new(targets.SessionAuthorization)
if err := json.Unmarshal([]byte(authzString), c.sessionAuthz); err == nil {
authzString = c.sessionAuthz.AuthorizationToken
}
}
@@ -387,7 +393,36 @@ func (c *Command) Run(args []string) (retCode int) {
c.PrintCliError(fmt.Errorf("Error trying to authorize a session against target: %w", err))
return base.CommandCliError
}
authzString = sar.GetItem().(*targets.SessionAuthorization).AuthorizationToken
c.sessionAuthz = sar.GetItem().(*targets.SessionAuthorization)
authzString = c.sessionAuthz.AuthorizationToken
}
if c.sessionAuthz != nil {
for _, cred := range c.sessionAuthz.Credentials {
// Since connect commands are helpers and are not meant as a direct CLI
// representation of the REST API, we can modify the credentials to be
// useful as a helper instead of trying to keep it as a faithful
// representation of what the api returns.
if len(cred.Secret) == 0 {
continue
}
switch cred.CredentialLibrary.Type {
case "vault":
// If it's Vault, the result will be JSON, except in specific
// circumstances that aren't used for credential fetching. So we
// can take the bytes as-is (after base64-decoding), but we'll
// format it nicely.
in, err := base64.StdEncoding.DecodeString(strings.Trim(string(cred.Secret), `"`))
if err != nil {
c.PrintCliError(fmt.Errorf("Error decoding secret as base64: %w", err))
return base.CommandCliError
}
// Now that it's decoded, pop it back in the same place as marshaled JSON
cred.Secret = in
default:
// If it's not Vault, and not another known type, leave it alone.
}
}
}
marshaled, err := base58.FastBase58Decoding(authzString)
@@ -481,7 +516,19 @@ func (c *Command) Run(args []string) (retCode int) {
c.listenerAddr = c.listener.Addr().(*net.TCPAddr)
if c.flagExec == "" {
var creds []*targets.SessionCredential
if c.sessionAuthz != nil && len(c.sessionAuthz.Credentials) > 0 {
creds = c.sessionAuthz.Credentials
}
switch c.Func {
case "postgres":
// Credentials are brokered when connecting to the postgres db.
// TODO: Figure out how to handle cases where we don't automatically know how to
// broker the credentials like unrecognized or multiple credentials.
case "connect":
// "connect" indicates there is no subcommand to the connect function.
// The only way a user will be able to connect to the session is by
// connecting directly to the port and address we report to them here.
sessInfo := SessionInfo{
Protocol: "tcp",
Address: c.listenerAddr.IP.String(),
@@ -489,8 +536,8 @@ func (c *Command) Run(args []string) (retCode int) {
Expiration: c.expiration,
ConnectionLimit: c.sessionAuthzData.GetConnectionLimit(),
SessionId: c.sessionAuthzData.GetSessionId(),
Credentials: creds,
}
switch base.Format(c.UI) {
case "table":
c.UI.Output(generateSessionInfoTableOutput(sessInfo))
@@ -502,6 +549,25 @@ func (c *Command) Run(args []string) (retCode int) {
}
c.UI.Output(string(out))
}
default:
if len(creds) == 0 {
break
}
switch base.Format(c.UI) {
case "table":
c.UI.Output(generateCredentialTableOutput(creds))
case "json":
out, err := json.Marshal(&struct {
Credentials []*targets.SessionCredential `json:"credentials"`
}{
Credentials: c.sessionAuthz.Credentials,
})
if err != nil {
c.PrintCliError(fmt.Errorf("error marshaling session information: %w", err))
return base.CommandCliError
}
c.UI.Output(string(out))
}
}
c.connWg = new(sync.WaitGroup)
@@ -616,6 +682,12 @@ func (c *Command) Run(args []string) (retCode int) {
cancel()
}
for _, f := range c.cleanupFuncs {
if err := f(); err != nil {
c.PrintCliError(err)
}
}
if termInfo.Reason != "" {
switch base.Format(c.UI) {
case "table":
@@ -770,6 +842,8 @@ func (c *Command) handleExec(passthroughArgs []string) {
addr := c.listenerAddr.String()
var args []string
var envs []string
var argsErr error
switch c.Func {
case "http":
@@ -782,7 +856,13 @@ func (c *Command) handleExec(passthroughArgs []string) {
args = append(args, httpArgs...)
case "postgres":
args = append(args, c.postgresFlags.buildArgs(c, port, ip, addr)...)
pgArgs, pgEnvs, pgErr := c.postgresFlags.buildArgs(c, port, ip, addr)
if pgErr != nil {
argsErr = pgErr
break
}
args = append(args, pgArgs...)
envs = append(envs, pgEnvs...)
case "rdp":
args = append(args, c.rdpFlags.buildArgs(c, port, ip, addr)...)
@@ -800,6 +880,12 @@ func (c *Command) handleExec(passthroughArgs []string) {
args = append(args, kubeArgs...)
}
if argsErr != nil {
c.PrintCliError(fmt.Errorf("Failed to collect args: %w", argsErr))
c.execCmdReturnValue.Store(int32(2))
return
}
args = append(passthroughArgs, args...)
stringReplacer := func(in, typ, replacer string) string {
@@ -824,11 +910,14 @@ func (c *Command) handleExec(passthroughArgs []string) {
// terminal in a weird state. It suffices to simply close the connection,
// which already happens, so we don't need/want CommandContext here.
cmd := exec.Command(c.flagExec, args...)
// Add original and network related envs here
cmd.Env = append(os.Environ(),
fmt.Sprintf("BOUNDARY_PROXIED_PORT=%s", port),
fmt.Sprintf("BOUNDARY_PROXIED_IP=%s", ip),
fmt.Sprintf("BOUNDARY_PROXIED_ADDR=%s", addr),
)
// Envs that came from subcommand handling
cmd.Env = append(cmd.Env, envs...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
+61
View File
@@ -1,8 +1,13 @@
package connect
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/internal/cmd/base"
)
@@ -23,10 +28,66 @@ func generateSessionInfoTableOutput(in SessionInfo) string {
"Proxy listening information:",
base.WrapMap(2, maxLength+2, nonAttributeMap),
}
if len(in.Credentials) > 0 {
ret = append(ret,
"")
ret = append(ret,
generateCredentialTableOutputSlice(2, in.Credentials)...)
}
return base.WrapForHelpText(ret)
}
func generateCredentialTableOutput(creds []*targets.SessionCredential) string {
return base.WrapForHelpText(generateCredentialTableOutputSlice(0, creds))
}
func generateCredentialTableOutputSlice(prefixIndent int, creds []*targets.SessionCredential) []string {
var ret []string
prefixString := strings.Repeat(" ", prefixIndent)
for _, crd := range creds {
libMap := map[string]interface{}{
"Credential Store ID": crd.CredentialLibrary.CredentialStoreId,
"Credential Library ID": crd.CredentialLibrary.Id,
"Credential Store Type": crd.CredentialLibrary.Type,
}
if crd.CredentialLibrary.Name != "" {
libMap["Credential Library Name"] = crd.CredentialLibrary.Name
}
if crd.CredentialLibrary.Description != "" {
libMap["Credential Library Description"] = crd.CredentialLibrary.Description
}
maxLength := base.MaxAttributesLength(libMap, nil, nil)
ret = append(ret,
fmt.Sprintf("%sCredentials:", prefixString),
base.WrapMap(2+prefixIndent, maxLength, libMap),
fmt.Sprintf("%s Secret:", prefixString))
ret = append(ret,
fmtSecretForTable(2+prefixIndent, crd)...,
)
}
return ret
}
func fmtSecretForTable(indent int, sc *targets.SessionCredential) []string {
prefixStr := strings.Repeat(" ", indent)
origSecret := []string{fmt.Sprintf("%s %s", prefixStr, sc.Secret)}
switch sc.CredentialLibrary.Type {
case "vault":
dst := new(bytes.Buffer)
if err := json.Indent(dst, sc.Secret, fmt.Sprintf("%s ", prefixStr), fmt.Sprintf("%s ", prefixStr)); err != nil {
return origSecret
}
secretStr := strings.Split(dst.String(), "\n")
if len(secretStr) > 0 {
secretStr[0] = fmt.Sprintf("%s %s", prefixStr, secretStr[0])
}
return secretStr
}
return origSecret
}
func generateConnectionInfoTableOutput(in ConnectionInfo) string {
var ret []string
+74 -5
View File
@@ -1,6 +1,10 @@
package connect
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/hashicorp/boundary/internal/cmd/base"
@@ -28,7 +32,15 @@ func postgresOptions(c *Command, set *base.FlagSets) {
Target: &c.flagUsername,
EnvVar: "BOUNDARY_CONNECT_USERNAME",
Completion: complete.PredictNothing,
Usage: `Specifies the username to pass through to the client`,
Usage: `Specifies the username to pass through to the client. May be overridden by credentials sourced from a credential store.`,
})
f.StringVar(&base.StringVar{
Name: "dbname",
Target: &c.flagDbname,
EnvVar: "BOUNDARY_CONNECT_DBNAME",
Completion: complete.PredictNothing,
Usage: `Specifies the database name to pass through to the client.`,
})
}
@@ -40,14 +52,71 @@ func (p *postgresFlags) defaultExec() string {
return strings.ToLower(p.flagPostgresStyle)
}
func (p *postgresFlags) buildArgs(c *Command, port, ip, addr string) []string {
var args []string
type postgresCredentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
func (p *postgresFlags) buildArgs(c *Command, port, ip, addr string) (args, envs []string, retErr error) {
var creds postgresCredentials
if c.sessionAuthz != nil && len(c.sessionAuthz.Credentials) > 0 {
for _, cred := range c.sessionAuthz.Credentials {
// TODO: Could allow switching on library ID or name
switch cred.CredentialLibrary.Type {
case "vault":
// Attempt unmarshaling into creds
if err := json.Unmarshal(cred.Secret, &creds); err != nil {
return nil, nil, fmt.Errorf("Error unmarshaling Vault secret: %w", err)
}
}
if creds.Username != "" && creds.Password != "" {
// In the future we can look for other types if we support other
// authentication mechanisms
break
}
}
}
switch p.flagPostgresStyle {
case "psql":
args = append(args, "-p", port, "-h", ip)
if c.flagUsername != "" {
if c.flagDbname != "" {
args = append(args, "-d", c.flagDbname)
}
switch {
case creds.Username != "":
args = append(args, "-U", creds.Username)
case c.flagUsername != "":
args = append(args, "-U", c.flagUsername)
}
if creds.Password != "" {
passfile, err := ioutil.TempFile("", "*")
if err != nil {
return nil, nil, fmt.Errorf("Error saving postgres password to tmp file: %w", err)
}
c.cleanupFuncs = append(c.cleanupFuncs, func() error {
if err := os.Remove(passfile.Name()); err != nil {
return fmt.Errorf("Error removing temporary password file; consider removing %s manually: %w", passfile.Name(), err)
}
return nil
})
_, err = passfile.WriteString(fmt.Sprintf("*:*:*:*:%s", creds.Password))
if err != nil {
return nil, nil, fmt.Errorf("Error writing password file to %s: %w", passfile.Name(), err)
}
if err := passfile.Close(); err != nil {
return nil, nil, fmt.Errorf("Error closing password file after writing to %s: %w", passfile.Name(), err)
}
envs = append(envs, fmt.Sprintf("PGPASSFILE=%s", passfile.Name()))
if c.flagDbname == "" {
c.UI.Warn("Credentials are being brokered but no -dbname parameter provided. psql may misinterpret another parameter as the database name.")
}
}
}
return args
return
}
+95 -48
View File
@@ -13,6 +13,7 @@ import (
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/credential"
"github.com/hashicorp/boundary/sdk/strutil"
"github.com/mitchellh/go-wordwrap"
"github.com/posener/complete"
@@ -28,10 +29,10 @@ func init() {
}
type extraCmdVars struct {
flagHostSets []string
flagCredentialLibraries []string
flagHostId string
sar *targets.SessionAuthorizationResult
flagHostSets []string
flagApplicationCredentialLibraries []string
flagHostId string
sar *targets.SessionAuthorizationResult
}
func extraActionsFlagsMapFuncImpl() map[string][]string {
@@ -40,9 +41,9 @@ func extraActionsFlagsMapFuncImpl() map[string][]string {
"add-host-sets": {"id", "host-set", "version"},
"remove-host-sets": {"id", "host-set", "version"},
"set-host-sets": {"id", "host-set", "version"},
"add-credential-libraries": {"id", "credential-library", "version"},
"remove-credential-libraries": {"id", "credential-library", "version"},
"set-credential-libraries": {"id", "credential-library", "version"},
"add-credential-libraries": {"id", "application-credential-library", "version"},
"remove-credential-libraries": {"id", "application-credential-library", "version"},
"set-credential-libraries": {"id", "application-credential-library", "version"},
}
}
@@ -163,7 +164,7 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
"",
" Add credential-library resources to a tcp-type target:",
"",
` $ boundary targets add-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890 -credential-library clvlt_0987654321`,
` $ boundary targets add-credential-libraries -id ttcp_1234567890 -application-credential-library clvlt_1234567890 -application-credential-library clvlt_0987654321`,
"",
"",
})
@@ -175,7 +176,7 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
"",
" Remove credential-library resources from a tcp-type target:",
"",
` $ boundary targets remove-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890 -credential-library clvlt_0987654321`,
` $ boundary targets remove-credential-libraries -id ttcp_1234567890 -application-credential-library clvlt_1234567890 -application-credential-library clvlt_0987654321`,
"",
"",
})
@@ -187,7 +188,7 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string {
"",
" Set credential-library resources on a tcp-type target:",
"",
` $ boundary targets set-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890`,
` $ boundary targets set-credential-libraries -id ttcp_1234567890 -application-credential-library clvlt_1234567890`,
"",
"",
})
@@ -226,11 +227,11 @@ func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) {
Target: &c.flagHostId,
Usage: "The ID of a specific host to connect to out of the hosts from the target's host sets. If not specified, one is chosen at random.",
})
case "credential-library":
case "application-credential-library":
f.StringSliceVar(&base.StringSliceVar{
Name: "credential-library",
Target: &c.flagCredentialLibraries,
Usage: "The credential-library resource to add, remove, or set. May be specified multiple times.",
Name: "application-credential-library",
Target: &c.flagApplicationCredentialLibraries,
Usage: "The credential-library resource for application purpose to add, set, or remove. May be specified multiple times.",
})
}
}
@@ -314,20 +315,27 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]targets.Op
}
case "add-credential-libraries", "remove-credential-libraries":
if len(c.flagCredentialLibraries) == 0 {
c.UI.Error("No credential-library supplied via -credential-library")
// TODO: As we add other purposes, add them to this check
if len(c.flagApplicationCredentialLibraries) == 0 {
c.UI.Error("No credential-libraries supplied via -application-credential-library")
return false
}
*opts = append(*opts, targets.WithApplicationCredentialLibraryIds(c.flagApplicationCredentialLibraries))
case "set-credential-libraries":
switch len(c.flagCredentialLibraries) {
case 0:
c.UI.Error("No credential-libraries supplied via -credential-library")
// TODO: As we add other purposes, add them to this check
if len(c.flagApplicationCredentialLibraries) == 0 {
c.UI.Error("No credential-libraries supplied via -application-credential-library")
return false
}
switch len(c.flagApplicationCredentialLibraries) {
case 0:
case 1:
if c.flagCredentialLibraries[0] == "null" {
c.flagCredentialLibraries = nil
if c.flagApplicationCredentialLibraries[0] == "null" {
*opts = append(*opts, targets.DefaultApplicationCredentialLibraryIds())
}
default:
*opts = append(*opts, targets.WithApplicationCredentialLibraryIds(c.flagApplicationCredentialLibraries))
}
case "authorize-session":
@@ -348,11 +356,11 @@ func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError
case "set-host-sets":
return targetClient.SetHostSets(c.Context, c.FlagId, version, c.flagHostSets, opts...)
case "add-credential-libraries":
return targetClient.AddCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...)
return targetClient.AddCredentialLibraries(c.Context, c.FlagId, version, opts...)
case "remove-credential-libraries":
return targetClient.RemoveCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...)
return targetClient.RemoveCredentialLibraries(c.Context, c.FlagId, version, opts...)
case "set-credential-libraries":
return targetClient.SetCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...)
return targetClient.SetCredentialLibraries(c.Context, c.FlagId, version, opts...)
case "authorize-session":
var err error
c.plural = "a session against target"
@@ -472,15 +480,20 @@ func printItemTable(result api.GenericResult) string {
}
}
var libraryMaps []map[string]interface{}
if len(item.CredentialLibraries) > 0 {
for _, lib := range item.CredentialLibraries {
var libraryMaps map[credential.Purpose][]map[string]interface{}
if len(item.ApplicationCredentialLibraries) > 0 {
if libraryMaps == nil {
libraryMaps = make(map[credential.Purpose][]map[string]interface{})
}
var applicationLibraryMaps []map[string]interface{}
for _, lib := range item.ApplicationCredentialLibraries {
m := map[string]interface{}{
"ID": lib.Id,
"Credential Store ID": lib.CredentialStoreId,
}
libraryMaps = append(libraryMaps, m)
applicationLibraryMaps = append(applicationLibraryMaps, m)
}
libraryMaps[credential.ApplicationPurpose] = applicationLibraryMaps
if l := len("Credential Store ID"); l > maxLength {
maxLength = l
}
@@ -524,15 +537,17 @@ func printItemTable(result api.GenericResult) string {
}
}
if len(item.CredentialLibraries) > 0 {
ret = append(ret,
" Credential Libraries:",
)
for _, m := range libraryMaps {
if len(libraryMaps) > 0 {
if appMap := libraryMaps[credential.ApplicationPurpose]; len(appMap) > 0 {
ret = append(ret,
base.WrapMap(4, maxLength, m),
"",
" Application Credential Libraries:",
)
for _, m := range appMap {
ret = append(ret,
base.WrapMap(4, maxLength, m),
"",
)
}
}
}
@@ -589,6 +604,10 @@ func printCustomActionOutputImpl(c *Command) (bool, error) {
)
for _, cred := range item.Credentials {
if len(cred.Secret) == 0 {
continue
}
ret = append(ret,
fmt.Sprintf(" Credential Store ID: %s", cred.CredentialLibrary.CredentialStoreId),
fmt.Sprintf(" Credential Library ID: %s", cred.CredentialLibrary.Id),
@@ -602,7 +621,7 @@ func printCustomActionOutputImpl(c *Command) (bool, error) {
ret = append(ret,
fmt.Sprintf(" Credential Library Description: %s", cred.CredentialLibrary.Description))
}
var secretStr string
var secretStr []string
switch cred.CredentialLibrary.Type {
case "vault":
// If it's Vault, the result will be JSON, except in
@@ -610,26 +629,28 @@ func printCustomActionOutputImpl(c *Command) (bool, error) {
// credential fetching. So we can take the bytes
// as-is (after base64-decoding), but we'll format
// it nicely.
in, err := base64.StdEncoding.DecodeString(cred.Secret)
in, err := base64.StdEncoding.DecodeString(strings.Trim(string(cred.Secret), `"`))
if err != nil {
return false, fmt.Errorf("Error decoding secret as base64: %w", err)
}
dst := new(bytes.Buffer)
if err := json.Indent(dst, in, "", " "); err != nil {
if err := json.Indent(dst, in, " ", " "); err != nil {
return false, fmt.Errorf("Error pretty-printing JSON: %w", err)
}
secretStr = dst.String()
secretStr = strings.Split(dst.String(), "\n")
if len(secretStr) > 0 {
// Indent doesn't apply to the first line 🙄
secretStr[0] = fmt.Sprintf(" %s", secretStr[0])
}
default:
// If it's not Vault, and not another known type,
// print out the base64-encoded value and leave it
// to the user to sort out.
secretStr = fmt.Sprintf(" %s", secretStr)
secretStr = []string{fmt.Sprintf(" %s", secretStr)}
}
ret = append(ret,
fmt.Sprintf(" Secret:"),
fmt.Sprintf(" %s", secretStr),
"",
)
ret = append(ret, " Secret:")
ret = append(ret, secretStr...)
ret = append(ret, "")
}
}
@@ -637,7 +658,33 @@ func printCustomActionOutputImpl(c *Command) (bool, error) {
return true, nil
case "json":
if ok := c.PrintJsonItem(c.sar); !ok {
if len(item.Credentials) > 0 {
for _, cred := range item.Credentials {
if len(cred.Secret) == 0 {
continue
}
switch cred.CredentialLibrary.Type {
case "vault":
// If it's Vault, the result will be JSON, except in
// specific circumstances that aren't used for
// credential fetching. So we can take the bytes
// as-is (after base64-decoding), but we'll format
// it nicely.
in, err := base64.StdEncoding.DecodeString(strings.Trim(string(cred.Secret), `"`))
if err != nil {
return false, fmt.Errorf("Error decoding secret as base64: %w", err)
}
// Now that it's decoded, pop it back in the same place
// as marshaled JSON
cred.Secret = in
default:
// If it's not Vault, and not another known type,
// leave it alone.
}
}
}
if ok := c.PrintJsonItem(c.sar, base.WithDecodedCredentials(item.Credentials)); !ok {
return false, fmt.Errorf("Error formatting as JSON")
}
return true, nil
@@ -675,8 +722,8 @@ func exampleOutput() string {
HostCatalogId: "hcst_1234567890",
},
},
CredentialLibraryIds: []string{"clvlt_1234567890", "clvlt_0987654321"},
CredentialLibraries: []*targets.CredentialLibrary{
ApplicationCredentialLibraryIds: []string{"clvlt_1234567890", "clvlt_0987654321"},
ApplicationCredentialLibraries: []*targets.CredentialLibrary{
{
Id: "clvlt_1234567890",
CredentialStoreId: "csvlt_1234567890",
@@ -59,30 +59,30 @@ func (c *TcpCommand) extraTcpHelpFunc(helpMap map[string]func() string) string {
}
func extraTcpFlagsFuncImpl(c *TcpCommand, set *base.FlagSets, f *base.FlagSet) {
f = set.NewFlagSet("TCP Target Options")
fs := set.NewFlagSet("TCP Target Options")
for _, name := range flagsTcpMap[c.Func] {
switch name {
case "default-port":
f.StringVar(&base.StringVar{
fs.StringVar(&base.StringVar{
Name: "default-port",
Target: &c.flagDefaultPort,
Usage: "The default port to set on the target.",
})
case "session-max-seconds":
f.StringVar(&base.StringVar{
fs.StringVar(&base.StringVar{
Name: "session-max-seconds",
Target: &c.flagSessionMaxSeconds,
Usage: `The maximum lifetime of the session, including all connections. Can be specified as an integer number of seconds or a duration string.`,
})
case "session-connection-limit":
f.StringVar(&base.StringVar{
fs.StringVar(&base.StringVar{
Name: "session-connection-limit",
Target: &c.flagSessionConnectionLimit,
Usage: "The maximum number of connections allowed for a session. -1 means unlimited.",
})
case "worker-filter":
f.StringVar(&base.StringVar{
fs.StringVar(&base.StringVar{
Name: "worker-filter",
Target: &c.flagWorkerFilter,
Usage: "A boolean expression to filter which workers can handle sessions for this target.",