179 lines
6.0 KiB
Go
179 lines
6.0 KiB
Go
// Copyright IBM Corp. 2020, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package credentialscmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/boundary/api/credentials"
|
|
"github.com/hashicorp/boundary/internal/cmd/base"
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
"github.com/hashicorp/go-secure-stdlib/password"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
func init() {
|
|
extraSshPrivateKeyFlagsFunc = extraSshPrivateKeyFlagsFuncImpl
|
|
extraSshPrivateKeyActionsFlagsMapFunc = extraSshPrivateKeyActionsFlagsMapFuncImpl
|
|
extraSshPrivateKeyFlagsHandlingFunc = extraSshPrivateKeyFlagHandlingFuncImpl
|
|
}
|
|
|
|
type extraSshPrivateKeyCmdVars struct {
|
|
flagUsername string
|
|
flagPrivateKey string
|
|
flagPrivateKeyPassphrase string
|
|
}
|
|
|
|
func extraSshPrivateKeyActionsFlagsMapFuncImpl() map[string][]string {
|
|
flags := map[string][]string{
|
|
"create": {
|
|
usernameFlagName,
|
|
privateKeyFlagName,
|
|
privateKeyPassphraseFlagName,
|
|
},
|
|
}
|
|
flags["update"] = flags["create"]
|
|
return flags
|
|
}
|
|
|
|
func extraSshPrivateKeyFlagsFuncImpl(c *SshPrivateKeyCommand, set *base.FlagSets, _ *base.FlagSet) {
|
|
f := set.NewFlagSet("SSH Private Key Credential Options")
|
|
|
|
for _, name := range flagsSshPrivateKeyMap[c.Func] {
|
|
switch name {
|
|
case usernameFlagName:
|
|
f.StringVar(&base.StringVar{
|
|
Name: usernameFlagName,
|
|
Target: &c.flagUsername,
|
|
Usage: "The username associated with the credential.",
|
|
})
|
|
case privateKeyFlagName:
|
|
f.StringVar(&base.StringVar{
|
|
Name: privateKeyFlagName,
|
|
Target: &c.flagPrivateKey,
|
|
Usage: "The SSH private key associated with the credential. This can refer to a file on disk (file://) from which the value will be read or an env var (env://) from which the value will be read.",
|
|
})
|
|
case privateKeyPassphraseFlagName:
|
|
f.StringVar(&base.StringVar{
|
|
Name: privateKeyPassphraseFlagName,
|
|
Target: &c.flagPrivateKeyPassphrase,
|
|
Usage: "The passphrase associated with the SSH private key. This value is ignored if the private key does not require a passphrase or if no private key is supplied. This can refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read. Or, if left empty, if the key requires a passphrase it can be entered manually.",
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func extraSshPrivateKeyFlagHandlingFuncImpl(c *SshPrivateKeyCommand, _ *base.FlagSets, opts *[]credentials.Option) bool {
|
|
switch c.flagUsername {
|
|
case "":
|
|
default:
|
|
*opts = append(*opts, credentials.WithSshPrivateKeyCredentialUsername(c.flagUsername))
|
|
}
|
|
|
|
// If private key not set (e.g. just a username update) then don't check
|
|
// either private key or passphrase
|
|
if c.flagPrivateKey == "" {
|
|
return true
|
|
}
|
|
|
|
// First parse private key
|
|
privateKey, err := parseutil.MustParsePath(c.flagPrivateKey)
|
|
switch {
|
|
case err == nil:
|
|
case errors.Is(err, parseutil.ErrNotAUrl), errors.Is(err, parseutil.ErrNotParsed):
|
|
c.UI.Error("Private key flag must be used with env:// or file:// syntax")
|
|
return false
|
|
default:
|
|
c.UI.Error(fmt.Sprintf("Error parsing private key flag: %v", err))
|
|
return false
|
|
}
|
|
|
|
// Now validate it
|
|
_, err = ssh.ParsePrivateKey([]byte(privateKey))
|
|
|
|
// If nil, parsed successfully without passphrase, ignore passphrase flag
|
|
if err == nil {
|
|
if c.flagPrivateKeyPassphrase != "" {
|
|
c.UI.Warn("Ignoring private key passphrase as private key is not encrypted.")
|
|
}
|
|
*opts = append(*opts, credentials.WithSshPrivateKeyCredentialPrivateKey(privateKey))
|
|
return true
|
|
}
|
|
|
|
// If an error but it's not a passphrase missing error, send the error back
|
|
if err.Error() != (&ssh.PassphraseMissingError{}).Error() {
|
|
c.UI.Error(fmt.Sprintf("Error parsing private key: %v", err))
|
|
return false
|
|
}
|
|
|
|
// Passphrase required
|
|
var privateKeyPassphrase string
|
|
switch c.flagPrivateKeyPassphrase {
|
|
case "":
|
|
fmt.Print("Please enter the passphrase for the private key (it will be hidden): ")
|
|
privateKeyPassphrase, err = password.Read(os.Stdin)
|
|
fmt.Print("\n")
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("An error occurred attempting to read the passphrase. The raw error message is shown below but usually this is because you attempted to pipe a value into the command or you are executing outside of a terminal (TTY). The raw error was:\n\n%s", err.Error()))
|
|
return false
|
|
}
|
|
privateKeyPassphrase = strings.TrimSpace(privateKeyPassphrase)
|
|
|
|
default:
|
|
privateKeyPassphrase, err = parseutil.MustParsePath(c.flagPrivateKeyPassphrase)
|
|
switch {
|
|
case err == nil:
|
|
case errors.Is(err, parseutil.ErrNotParsed):
|
|
c.UI.Error("Private key passphrase flag must be used with env:// or file:// syntax")
|
|
return false
|
|
default:
|
|
c.UI.Error(fmt.Sprintf("Error parsing private key passphrase flag: %v", err))
|
|
return false
|
|
}
|
|
}
|
|
|
|
if _, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(privateKeyPassphrase)); err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error parsing private key passphrase: %v", err))
|
|
return false
|
|
}
|
|
|
|
*opts = append(*opts,
|
|
credentials.WithSshPrivateKeyCredentialPrivateKey(privateKey),
|
|
credentials.WithSshPrivateKeyCredentialPrivateKeyPassphrase(privateKeyPassphrase),
|
|
)
|
|
|
|
return true
|
|
}
|
|
|
|
func (c *SshPrivateKeyCommand) extraSshPrivateKeyHelpFunc(_ map[string]func() string) string {
|
|
var helpStr string
|
|
switch c.Func {
|
|
case "create":
|
|
helpStr = base.WrapForHelpText([]string{
|
|
"Usage: threshold credentials create ssh-private-key -credential-store-id [options] [args]",
|
|
"",
|
|
" Create an SSH private key credential. Example:",
|
|
"",
|
|
` $ threshold credentials create ssh-private-key -credential-store-id csvlt_1234567890 -username user -private-key file:///home/user/.ssh/id_ed25519`,
|
|
"",
|
|
"",
|
|
})
|
|
|
|
case "update":
|
|
helpStr = base.WrapForHelpText([]string{
|
|
"Usage: threshold credentials update ssh-private-key [options] [args]",
|
|
"",
|
|
" Update an SSH private key credential given its ID. Example:",
|
|
"",
|
|
` $ threshold credentials update ssh-private-key -id clvlt_1234567890 -name devops -description "For DevOps usage"`,
|
|
"",
|
|
"",
|
|
})
|
|
}
|
|
return helpStr + c.Flags().Help()
|
|
}
|