2022-08-08 15:15:03 -04:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2022-07-19 16:58:03 -04:00
package credentialscmd
import (
2022-08-05 16:08:48 -04:00
"errors"
"fmt"
2022-08-08 15:15:03 -04:00
"os"
"strings"
2022-08-05 16:08:48 -04:00
2022-07-19 16:58:03 -04:00
"github.com/hashicorp/boundary/api/credentials"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/go-secure-stdlib/parseutil"
2022-08-08 15:15:03 -04:00
"github.com/hashicorp/go-secure-stdlib/password"
"golang.org/x/crypto/ssh"
2022-07-19 16:58:03 -04:00
)
func init () {
extraSshPrivateKeyFlagsFunc = extraSshPrivateKeyFlagsFuncImpl
extraSshPrivateKeyActionsFlagsMapFunc = extraSshPrivateKeyActionsFlagsMapFuncImpl
extraSshPrivateKeyFlagsHandlingFunc = extraSshPrivateKeyFlagHandlingFuncImpl
}
type extraSshPrivateKeyCmdVars struct {
2022-08-08 15:15:03 -04:00
flagUsername string
flagPrivateKey string
flagPrivateKeyPassphrase string
2022-07-19 16:58:03 -04:00
}
func extraSshPrivateKeyActionsFlagsMapFuncImpl () map [ string ][] string {
flags := map [ string ][] string {
"create" : {
usernameFlagName ,
privateKeyFlagName ,
2022-08-08 15:15:03 -04:00
privateKeyPassphraseFlagName ,
2022-07-19 16:58:03 -04:00
},
}
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 ,
2022-08-08 15:15:03 -04:00
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." ,
2022-07-19 16:58:03 -04:00
})
}
}
}
func extraSshPrivateKeyFlagHandlingFuncImpl ( c * SshPrivateKeyCommand , _ * base . FlagSets , opts * [] credentials . Option ) bool {
switch c . flagUsername {
case "" :
default :
* opts = append ( * opts , credentials . WithSshPrivateKeyCredentialUsername ( c . flagUsername ))
}
2022-08-08 15:15:03 -04:00
// 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 :
2023-11-08 12:30:54 -05:00
case errors . Is ( err , parseutil . ErrNotAUrl ), errors . Is ( err , parseutil . ErrNotParsed ):
2022-08-08 15:15:03 -04:00
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 {
2022-07-19 16:58:03 -04:00
case "" :
2022-08-08 15:15:03 -04:00
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 )
2022-07-19 16:58:03 -04:00
default :
2022-08-08 15:15:03 -04:00
privateKeyPassphrase , err = parseutil . MustParsePath ( c . flagPrivateKeyPassphrase )
2022-08-05 16:08:48 -04:00
switch {
case err == nil :
case errors . Is ( err , parseutil . ErrNotParsed ):
2022-08-08 15:15:03 -04:00
c . UI . Error ( "Private key passphrase flag must be used with env:// or file:// syntax" )
2022-08-05 16:08:48 -04:00
return false
default :
2022-08-08 15:15:03 -04:00
c . UI . Error ( fmt . Sprintf ( "Error parsing private key passphrase flag: %v" , err ))
2022-07-19 16:58:03 -04:00
return false
}
}
2022-08-08 15:15:03 -04:00
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 ),
)
2022-07-19 16:58:03 -04:00
return true
}
func ( c * SshPrivateKeyCommand ) extraSshPrivateKeyHelpFunc ( _ map [ string ] func () string ) string {
var helpStr string
switch c . Func {
case "create" :
helpStr = base . WrapForHelpText ([] string {
"Usage: boundary credentials create ssh-private-key -credential-store-id [options] [args]" ,
"" ,
" Create an SSH private key credential. Example:" ,
"" ,
` $ boundary 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: boundary credentials update ssh-private-key [options] [args]" ,
"" ,
" Update an SSH private key credential given its ID. Example:" ,
"" ,
` $ boundary credentials update ssh-private-key -id clvlt_1234567890 -name devops -description "For DevOps usage"` ,
"" ,
"" ,
})
}
return helpStr + c . Flags (). Help ()
}