Update target credential library service, API, SDK, CLI (#1343)
This commit is contained in:
1 parent
83c65d0d4b
commit
86c9a90554
27 files changed
+1474
-1127
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.",
|
||||
|
||||
Reference in new issue
Block a user