2023-01-17 06:55:28 +00:00
// ssh.go
// Copyright (C) Andrew Woodlee 2023
// License: Apache-2.0
2022-12-27 05:20:11 +00:00
package backy
import (
2023-02-02 05:45:23 +00:00
"bufio"
"fmt"
2022-12-27 05:20:11 +00:00
"os"
"os/user"
2023-02-12 05:50:19 +00:00
"strconv"
2022-12-27 05:20:11 +00:00
"strings"
2023-02-02 05:45:23 +00:00
"time"
2022-12-27 05:20:11 +00:00
"github.com/kevinburke/ssh_config"
2023-02-02 05:45:23 +00:00
"github.com/pkg/errors"
2023-01-04 02:09:02 +00:00
"github.com/rs/zerolog"
2022-12-27 05:20:11 +00:00
"golang.org/x/crypto/ssh"
2023-01-03 02:02:54 +00:00
"golang.org/x/crypto/ssh/knownhosts"
2022-12-27 05:20:11 +00:00
)
2024-08-28 20:06:25 +00:00
var PrivateKeyExtraInfoErr = errors . New ( "Private key may be encrypted. \nIf encrypted, make sure the password is specified correctly in the correct section. This may be done in one of three ways: \n privatekeypassword: env:PR_KEY_PASS \n privatekeypassword: file:/path/to/password-file \n privatekeypassword: password (not recommended). \n " )
2023-02-02 05:45:23 +00:00
var TS = strings . TrimSpace
2023-01-17 06:55:28 +00:00
// ConnectToSSHHost connects to a host by looking up the config values in the directory ~/.ssh/config
2023-02-02 05:45:23 +00:00
// It uses any set values and looks up an unset values in the config files
2023-01-17 06:55:28 +00:00
// It returns an ssh.Client used to run commands against.
2023-02-12 05:50:19 +00:00
// If configFile is empty, any required configuration is looked up in the default config files
// If any value is not found, defaults are used
2023-09-09 04:42:13 +00:00
func ( remoteConfig * Host ) ConnectToSSHHost ( opts * ConfigOpts ) error {
2023-01-02 18:00:11 +00:00
2022-12-27 05:20:11 +00:00
var connectErr error
2023-02-02 05:45:23 +00:00
if TS ( remoteConfig . ConfigFilePath ) == "" {
remoteConfig . useDefaultConfig = true
}
2024-08-28 20:06:25 +00:00
khPathErr := remoteConfig . GetKnownHosts ( )
2023-02-02 05:45:23 +00:00
if khPathErr != nil {
2023-02-12 05:50:19 +00:00
return khPathErr
2023-02-02 05:45:23 +00:00
}
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if remoteConfig . ClientConfig == nil {
remoteConfig . ClientConfig = & ssh . ClientConfig { }
}
2024-08-28 20:06:25 +00:00
2023-02-12 05:50:19 +00:00
var configFile * os . File
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
var sshConfigFileOpenErr error
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if ! remoteConfig . useDefaultConfig {
2023-09-09 04:42:13 +00:00
var err error
remoteConfig . ConfigFilePath , err = resolveDir ( remoteConfig . ConfigFilePath )
if err != nil {
return err
}
2023-02-12 05:50:19 +00:00
configFile , sshConfigFileOpenErr = os . Open ( remoteConfig . ConfigFilePath )
2023-02-02 05:45:23 +00:00
if sshConfigFileOpenErr != nil {
2023-02-12 05:50:19 +00:00
return sshConfigFileOpenErr
2023-02-02 05:45:23 +00:00
}
} else {
defaultConfig , _ := resolveDir ( "~/.ssh/config" )
2023-02-12 05:50:19 +00:00
configFile , sshConfigFileOpenErr = os . Open ( defaultConfig )
2023-02-02 05:45:23 +00:00
if sshConfigFileOpenErr != nil {
2023-02-12 05:50:19 +00:00
return sshConfigFileOpenErr
2023-02-02 05:45:23 +00:00
}
}
2023-02-12 05:50:19 +00:00
remoteConfig . SSHConfigFile = & sshConfigFile { }
2023-02-02 05:45:23 +00:00
remoteConfig . SSHConfigFile . DefaultUserSettings = ssh_config . DefaultUserSettings
2023-02-12 05:50:19 +00:00
var decodeErr error
remoteConfig . SSHConfigFile . SshConfigFile , decodeErr = ssh_config . Decode ( configFile )
2023-02-02 05:45:23 +00:00
if decodeErr != nil {
2023-02-12 05:50:19 +00:00
return decodeErr
2023-02-02 05:45:23 +00:00
}
2023-02-19 04:42:15 +00:00
2023-09-09 04:42:13 +00:00
err := remoteConfig . GetProxyJumpFromConfig ( opts . Hosts )
2024-08-28 20:06:25 +00:00
2023-02-19 04:42:15 +00:00
if err != nil {
return err
}
2024-08-28 20:06:25 +00:00
2023-02-19 04:42:15 +00:00
if remoteConfig . ProxyHost != nil {
for _ , proxyHost := range remoteConfig . ProxyHost {
2023-09-09 04:42:13 +00:00
err := proxyHost . GetProxyJumpConfig ( opts . Hosts , opts )
opts . Logger . Info ( ) . Msgf ( "Proxy host: %s" , proxyHost . Host )
2023-02-19 04:42:15 +00:00
if err != nil {
return err
}
}
}
2023-03-10 22:01:02 +00:00
2023-02-12 05:50:19 +00:00
remoteConfig . ClientConfig . Timeout = time . Second * 30
2024-08-28 20:06:25 +00:00
2023-02-12 05:50:19 +00:00
remoteConfig . GetPrivateKeyFileFromConfig ( )
2024-08-28 20:06:25 +00:00
2023-02-12 05:50:19 +00:00
remoteConfig . GetPort ( )
2024-08-28 20:06:25 +00:00
2023-02-12 05:50:19 +00:00
remoteConfig . GetHostName ( )
2024-08-28 20:06:25 +00:00
2023-02-12 05:50:19 +00:00
remoteConfig . CombineHostNameWithPort ( )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteConfig . GetSshUserFromConfig ( )
2023-03-10 22:01:02 +00:00
2023-02-02 05:45:23 +00:00
if remoteConfig . HostName == "" {
2023-03-10 22:01:02 +00:00
return errors . Errorf ( "No hostname found or specified for host %s" , remoteConfig . Host )
2023-02-02 05:45:23 +00:00
}
2023-03-10 22:01:02 +00:00
2023-07-02 02:46:54 +00:00
err = remoteConfig . GetAuthMethods ( opts )
2023-02-02 05:45:23 +00:00
if err != nil {
2023-02-12 05:50:19 +00:00
return err
2023-02-02 05:45:23 +00:00
}
2024-08-28 20:06:25 +00:00
hostKeyCallback , err := knownhosts . New ( remoteConfig . KnownHostsFile )
2023-02-02 05:45:23 +00:00
if err != nil {
2023-02-12 05:50:19 +00:00
return errors . Wrap ( err , "could not create hostkeycallback function" )
2023-02-02 05:45:23 +00:00
}
remoteConfig . ClientConfig . HostKeyCallback = hostKeyCallback
2023-09-09 04:42:13 +00:00
opts . Logger . Info ( ) . Str ( "user" , remoteConfig . ClientConfig . User ) . Send ( )
2023-02-02 05:45:23 +00:00
2023-09-09 04:42:13 +00:00
remoteConfig . SshClient , connectErr = remoteConfig . ConnectThroughBastion ( opts . Logger )
2023-02-12 05:50:19 +00:00
if connectErr != nil {
return connectErr
}
if remoteConfig . SshClient != nil {
2023-09-09 04:42:13 +00:00
opts . Hosts [ remoteConfig . Host ] = remoteConfig
2023-02-12 05:50:19 +00:00
return nil
}
2023-09-09 04:42:13 +00:00
opts . Logger . Info ( ) . Msgf ( "Connecting to host %s" , remoteConfig . HostName )
2023-02-12 05:50:19 +00:00
remoteConfig . SshClient , connectErr = ssh . Dial ( "tcp" , remoteConfig . HostName , remoteConfig . ClientConfig )
2023-02-02 05:45:23 +00:00
if connectErr != nil {
2023-02-12 05:50:19 +00:00
return connectErr
2023-02-02 05:45:23 +00:00
}
2023-07-21 02:21:40 +00:00
2023-09-09 04:42:13 +00:00
opts . Hosts [ remoteConfig . Host ] = remoteConfig
2023-02-12 05:50:19 +00:00
return nil
2023-02-02 05:45:23 +00:00
}
func ( remoteHost * Host ) GetSshUserFromConfig ( ) {
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if TS ( remoteHost . User ) == "" {
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . User , _ = remoteHost . SSHConfigFile . SshConfigFile . Get ( remoteHost . Host , "User" )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if TS ( remoteHost . User ) == "" {
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . User = remoteHost . SSHConfigFile . DefaultUserSettings . Get ( remoteHost . Host , "User" )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if TS ( remoteHost . User ) == "" {
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
currentUser , _ := user . Current ( )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . User = currentUser . Username
2023-01-03 02:02:54 +00:00
}
2023-02-02 05:45:23 +00:00
}
}
remoteHost . ClientConfig . User = remoteHost . User
}
2023-07-21 02:21:40 +00:00
2023-07-02 02:46:54 +00:00
func ( remoteHost * Host ) GetAuthMethods ( opts * ConfigOpts ) error {
2023-02-02 05:45:23 +00:00
var signer ssh . Signer
var err error
var privateKey [ ] byte
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . Password = strings . TrimSpace ( remoteHost . Password )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . PrivateKeyPassword = strings . TrimSpace ( remoteHost . PrivateKeyPassword )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . PrivateKeyPath = strings . TrimSpace ( remoteHost . PrivateKeyPath )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if remoteHost . PrivateKeyPath != "" {
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
privateKey , err = os . ReadFile ( remoteHost . PrivateKeyPath )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if err != nil {
return err
}
2024-08-28 20:06:25 +00:00
2023-09-09 04:42:13 +00:00
remoteHost . PrivateKeyPassword , err = GetPrivateKeyPassword ( remoteHost . PrivateKeyPassword , opts , opts . Logger )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if err != nil {
return err
}
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if remoteHost . PrivateKeyPassword == "" {
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
signer , err = ssh . ParsePrivateKey ( privateKey )
2024-08-28 20:06:25 +00:00
2022-12-27 05:20:11 +00:00
if err != nil {
2023-02-19 04:42:15 +00:00
return errors . Errorf ( "Failed to open private key file %s: %v \n\n %v" , remoteHost . PrivateKeyPath , err , PrivateKeyExtraInfoErr )
2022-12-27 05:20:11 +00:00
}
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . ClientConfig . Auth = [ ] ssh . AuthMethod { ssh . PublicKeys ( signer ) }
} else {
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
signer , err = ssh . ParsePrivateKeyWithPassphrase ( privateKey , [ ] byte ( remoteHost . PrivateKeyPassword ) )
2024-08-28 20:06:25 +00:00
2022-12-27 05:20:11 +00:00
if err != nil {
2023-02-19 04:42:15 +00:00
return errors . Errorf ( "Failed to open private key file %s: %v \n\n %v" , remoteHost . PrivateKeyPath , err , PrivateKeyExtraInfoErr )
2022-12-27 05:20:11 +00:00
}
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . ClientConfig . Auth = [ ] ssh . AuthMethod { ssh . PublicKeys ( signer ) }
}
}
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if remoteHost . Password == "" {
2024-08-28 20:06:25 +00:00
2023-09-09 04:42:13 +00:00
remoteHost . Password , err = GetPassword ( remoteHost . Password , opts , opts . Logger )
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
if err != nil {
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
return err
}
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
remoteHost . ClientConfig . Auth = append ( remoteHost . ClientConfig . Auth , ssh . Password ( remoteHost . Password ) )
}
2024-08-28 20:06:25 +00:00
2023-02-02 05:45:23 +00:00
return nil
}
// GetPrivateKeyFromConfig checks to see if the privateKeyPath is empty.
// If not, it keeps the value.
// If empty, the key is looked for in the specified config file.
2023-02-12 05:50:19 +00:00
// If that path is empty, the default config file is searched.
2023-02-02 05:45:23 +00:00
// If not found in the default file, the privateKeyPath is set to ~/.ssh/id_rsa
2023-02-12 05:50:19 +00:00
func ( remoteHost * Host ) GetPrivateKeyFileFromConfig ( ) {
2023-02-02 05:45:23 +00:00
var identityFile string
if remoteHost . PrivateKeyPath == "" {
identityFile , _ = remoteHost . SSHConfigFile . SshConfigFile . Get ( remoteHost . Host , "IdentityFile" )
if identityFile == "" {
identityFile , _ = remoteHost . SSHConfigFile . DefaultUserSettings . GetStrict ( remoteHost . Host , "IdentityFile" )
if identityFile == "" {
identityFile = "~/.ssh/id_rsa"
2022-12-27 05:20:11 +00:00
}
2023-02-02 05:45:23 +00:00
}
}
if identityFile == "" {
identityFile = remoteHost . PrivateKeyPath
}
remoteHost . PrivateKeyPath , _ = resolveDir ( identityFile )
}
2023-02-12 05:50:19 +00:00
// GetPort checks if the port from the config file is 0
2023-02-02 17:24:01 +00:00
// If it is the port is searched in the SSH config file(s)
2023-02-12 05:50:19 +00:00
func ( remoteHost * Host ) GetPort ( ) {
2023-02-19 04:42:15 +00:00
port := fmt . Sprintf ( "%d" , remoteHost . Port )
2023-02-12 05:50:19 +00:00
// port specifed?
2024-08-28 20:06:25 +00:00
// port will be 0 if missing from backy config
2023-02-02 17:24:01 +00:00
if port == "0" {
2024-08-28 20:06:25 +00:00
// get port from specified SSH config file
2023-02-02 17:24:01 +00:00
port , _ = remoteHost . SSHConfigFile . SshConfigFile . Get ( remoteHost . Host , "Port" )
2024-08-28 20:06:25 +00:00
2023-02-02 17:24:01 +00:00
if port == "" {
2024-08-28 20:06:25 +00:00
// get port from default SSH config file
2023-02-02 17:24:01 +00:00
port = remoteHost . SSHConfigFile . DefaultUserSettings . Get ( remoteHost . Host , "Port" )
2024-08-28 20:06:25 +00:00
// set port to be default
2023-02-02 05:45:23 +00:00
if port == "" {
2023-02-02 17:24:01 +00:00
port = "22"
2023-02-02 05:45:23 +00:00
}
}
2023-02-02 17:24:01 +00:00
}
2023-02-19 04:42:15 +00:00
portNum , _ := strconv . ParseUint ( port , 10 , 16 )
2023-02-12 05:50:19 +00:00
remoteHost . Port = uint16 ( portNum )
}
func ( remoteHost * Host ) CombineHostNameWithPort ( ) {
2024-08-28 20:06:25 +00:00
// if the port is already in the HostName, leave it
if strings . HasSuffix ( remoteHost . HostName , fmt . Sprintf ( ":%d" , remoteHost . Port ) ) {
2023-02-12 14:29:55 +00:00
return
}
2024-08-28 20:06:25 +00:00
2023-02-19 04:42:15 +00:00
remoteHost . HostName = fmt . Sprintf ( "%s:%d" , remoteHost . HostName , remoteHost . Port )
2023-02-12 05:50:19 +00:00
}
func ( remoteHost * Host ) GetHostName ( ) {
if remoteHost . HostName == "" {
remoteHost . HostName , _ = remoteHost . SSHConfigFile . SshConfigFile . Get ( remoteHost . Host , "HostName" )
if remoteHost . HostName == "" {
remoteHost . HostName = remoteHost . SSHConfigFile . DefaultUserSettings . Get ( remoteHost . Host , "HostName" )
}
2023-02-02 05:45:23 +00:00
}
}
2023-07-02 02:46:54 +00:00
func ( remoteHost * Host ) ConnectThroughBastion ( log zerolog . Logger ) ( * ssh . Client , error ) {
2023-02-12 05:50:19 +00:00
if remoteHost . ProxyHost == nil {
return nil , nil
}
log . Info ( ) . Msgf ( "Connecting to proxy host %s" , remoteHost . ProxyHost [ 0 ] . HostName )
2023-02-02 05:45:23 +00:00
// connect to the bastion host
2023-02-12 05:50:19 +00:00
bClient , err := ssh . Dial ( "tcp" , remoteHost . ProxyHost [ 0 ] . HostName , remoteHost . ProxyHost [ 0 ] . ClientConfig )
2023-02-02 05:45:23 +00:00
if err != nil {
return nil , err
}
2023-03-10 22:01:02 +00:00
remoteHost . ProxyHost [ 0 ] . SshClient = bClient
2023-02-02 05:45:23 +00:00
// Dial a connection to the service host, from the bastion
conn , err := bClient . Dial ( "tcp" , remoteHost . HostName )
if err != nil {
return nil , err
}
2023-02-12 05:50:19 +00:00
log . Info ( ) . Msgf ( "Connecting to host %s" , remoteHost . HostName )
2023-02-02 05:45:23 +00:00
ncc , chans , reqs , err := ssh . NewClientConn ( conn , remoteHost . HostName , remoteHost . ClientConfig )
if err != nil {
2023-02-12 05:50:19 +00:00
return nil , err
2023-02-02 05:45:23 +00:00
}
// sClient is an ssh client connected to the service host, through the bastion host.
2024-08-28 20:06:25 +00:00
sClient := ssh . NewClient ( ncc , chans , reqs )
2023-02-02 05:45:23 +00:00
return sClient , nil
}
2024-08-28 20:06:25 +00:00
// GetKnownHosts resolves the host's KnownHosts file if it is defined
// if not defined, the default location for this file is used
func ( remotehHost * Host ) GetKnownHosts ( ) error {
var knownHostsFileErr error
if TS ( remotehHost . KnownHostsFile ) != "" {
remotehHost . KnownHostsFile , knownHostsFileErr = resolveDir ( remotehHost . KnownHostsFile )
return knownHostsFileErr
2023-02-02 05:45:23 +00:00
}
2024-08-28 20:06:25 +00:00
remotehHost . KnownHostsFile , knownHostsFileErr = resolveDir ( "~/.ssh/known_hosts" )
return knownHostsFileErr
2023-02-02 05:45:23 +00:00
}
2023-07-02 02:46:54 +00:00
func GetPrivateKeyPassword ( key string , opts * ConfigOpts , log zerolog . Logger ) ( string , error ) {
2023-07-21 02:21:40 +00:00
2023-02-02 05:45:23 +00:00
var prKeyPassword string
if strings . HasPrefix ( key , "file:" ) {
privKeyPassFilePath := strings . TrimPrefix ( key , "file:" )
privKeyPassFilePath , _ = resolveDir ( privKeyPassFilePath )
keyFile , keyFileErr := os . Open ( privKeyPassFilePath )
if keyFileErr != nil {
2023-02-19 04:42:15 +00:00
return "" , errors . Errorf ( "Private key password file %s failed to open. \n Make sure it is accessible and correct." , privKeyPassFilePath )
2022-12-27 05:20:11 +00:00
}
2023-02-02 05:45:23 +00:00
passwordScanner := bufio . NewScanner ( keyFile )
for passwordScanner . Scan ( ) {
prKeyPassword = passwordScanner . Text ( )
}
} else if strings . HasPrefix ( key , "env:" ) {
privKey := strings . TrimPrefix ( key , "env:" )
privKey = strings . TrimPrefix ( privKey , "${" )
privKey = strings . TrimSuffix ( privKey , "}" )
privKey = strings . TrimPrefix ( privKey , "$" )
prKeyPassword = os . Getenv ( privKey )
} else {
prKeyPassword = key
}
2023-09-09 04:42:13 +00:00
prKeyPassword = GetVaultKey ( prKeyPassword , opts , opts . Logger )
2023-02-02 05:45:23 +00:00
return prKeyPassword , nil
}
2022-12-27 05:20:11 +00:00
2024-08-28 20:06:25 +00:00
// GetPassword gets any password
2023-07-02 02:46:54 +00:00
func GetPassword ( pass string , opts * ConfigOpts , log zerolog . Logger ) ( string , error ) {
2023-07-21 02:21:40 +00:00
2023-02-12 05:50:19 +00:00
pass = strings . TrimSpace ( pass )
if pass == "" {
2023-02-02 05:45:23 +00:00
return "" , nil
}
var password string
2023-02-12 05:50:19 +00:00
if strings . HasPrefix ( pass , "file:" ) {
passFilePath := strings . TrimPrefix ( pass , "file:" )
2023-02-02 05:45:23 +00:00
passFilePath , _ = resolveDir ( passFilePath )
keyFile , keyFileErr := os . Open ( passFilePath )
if keyFileErr != nil {
return "" , errors . New ( "Password file failed to open" )
}
passwordScanner := bufio . NewScanner ( keyFile )
for passwordScanner . Scan ( ) {
password = passwordScanner . Text ( )
}
2023-02-12 05:50:19 +00:00
} else if strings . HasPrefix ( pass , "env:" ) {
passEnv := strings . TrimPrefix ( pass , "env:" )
2023-02-02 05:45:23 +00:00
passEnv = strings . TrimPrefix ( passEnv , "${" )
passEnv = strings . TrimSuffix ( passEnv , "}" )
passEnv = strings . TrimPrefix ( passEnv , "$" )
password = os . Getenv ( passEnv )
} else {
2023-02-12 05:50:19 +00:00
password = pass
2022-12-27 05:20:11 +00:00
}
2023-09-09 04:42:13 +00:00
password = GetVaultKey ( password , opts , opts . Logger )
2023-07-02 02:46:54 +00:00
2023-02-02 05:45:23 +00:00
return password , nil
2022-12-27 05:20:11 +00:00
}
2023-02-12 05:50:19 +00:00
func ( remoteConfig * Host ) GetProxyJumpFromConfig ( hosts map [ string ] * Host ) error {
2023-07-21 02:21:40 +00:00
2023-02-12 05:50:19 +00:00
proxyJump , _ := remoteConfig . SSHConfigFile . SshConfigFile . Get ( remoteConfig . Host , "ProxyJump" )
if proxyJump == "" {
proxyJump = remoteConfig . SSHConfigFile . DefaultUserSettings . Get ( remoteConfig . Host , "ProxyJump" )
}
if remoteConfig . ProxyJump == "" && proxyJump != "" {
remoteConfig . ProxyJump = proxyJump
}
proxyJumpHosts := strings . Split ( remoteConfig . ProxyJump , "," )
if remoteConfig . ProxyHost == nil && len ( proxyJumpHosts ) == 1 {
remoteConfig . ProxyJump = proxyJump
proxyHost , proxyHostFound := hosts [ proxyJump ]
if proxyHostFound {
remoteConfig . ProxyHost = append ( remoteConfig . ProxyHost , proxyHost )
} else {
2023-02-19 04:42:15 +00:00
if proxyJump != "" {
newProxy := & Host { Host : proxyJump }
remoteConfig . ProxyHost = append ( remoteConfig . ProxyHost , newProxy )
}
2023-02-12 05:50:19 +00:00
}
}
return nil
}
2023-03-10 22:01:02 +00:00
2023-07-02 02:46:54 +00:00
func ( remoteConfig * Host ) GetProxyJumpConfig ( hosts map [ string ] * Host , opts * ConfigOpts ) error {
2023-07-21 02:21:40 +00:00
2023-02-12 05:50:19 +00:00
if TS ( remoteConfig . ConfigFilePath ) == "" {
remoteConfig . useDefaultConfig = true
}
2024-08-28 20:06:25 +00:00
khPathErr := remoteConfig . GetKnownHosts ( )
2023-02-12 05:50:19 +00:00
if khPathErr != nil {
return khPathErr
}
if remoteConfig . ClientConfig == nil {
remoteConfig . ClientConfig = & ssh . ClientConfig { }
}
var configFile * os . File
var sshConfigFileOpenErr error
if ! remoteConfig . useDefaultConfig {
configFile , sshConfigFileOpenErr = os . Open ( remoteConfig . ConfigFilePath )
if sshConfigFileOpenErr != nil {
return sshConfigFileOpenErr
}
} else {
defaultConfig , _ := resolveDir ( "~/.ssh/config" )
configFile , sshConfigFileOpenErr = os . Open ( defaultConfig )
if sshConfigFileOpenErr != nil {
return sshConfigFileOpenErr
}
}
remoteConfig . SSHConfigFile = & sshConfigFile { }
remoteConfig . SSHConfigFile . DefaultUserSettings = ssh_config . DefaultUserSettings
var decodeErr error
remoteConfig . SSHConfigFile . SshConfigFile , decodeErr = ssh_config . Decode ( configFile )
if decodeErr != nil {
return decodeErr
}
remoteConfig . GetPrivateKeyFileFromConfig ( )
remoteConfig . GetPort ( )
remoteConfig . GetHostName ( )
remoteConfig . CombineHostNameWithPort ( )
remoteConfig . GetSshUserFromConfig ( )
2023-03-14 01:25:27 +00:00
remoteConfig . isProxyHost = true
2023-02-12 05:50:19 +00:00
if remoteConfig . HostName == "" {
2023-03-10 22:01:02 +00:00
return errors . Errorf ( "No hostname found or specified for host %s" , remoteConfig . Host )
2023-02-12 05:50:19 +00:00
}
2023-07-02 02:46:54 +00:00
err := remoteConfig . GetAuthMethods ( opts )
2023-02-12 05:50:19 +00:00
if err != nil {
return err
}
// TODO: Add value/option to config for host key and add bool to check for host key
2024-08-28 20:06:25 +00:00
hostKeyCallback , err := knownhosts . New ( remoteConfig . KnownHostsFile )
2023-02-12 05:50:19 +00:00
if err != nil {
2023-07-22 01:29:07 +00:00
return fmt . Errorf ( "could not create hostkeycallback function: %v" , err )
2023-02-12 05:50:19 +00:00
}
remoteConfig . ClientConfig . HostKeyCallback = hostKeyCallback
2023-03-10 22:01:02 +00:00
hosts [ remoteConfig . Host ] = remoteConfig
2023-02-12 05:50:19 +00:00
return nil
}