You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
backy/pkg/backy/ssh.go

100 lines
3.2 KiB

// ssh.go
// Copyright (C) Andrew Woodlee 2023
// License: Apache-2.0
package backy
import (
"os"
"os/user"
"path/filepath"
"strings"
"github.com/kevinburke/ssh_config"
"github.com/rs/zerolog"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
// ConnectToSSHHost connects to a host by looking up the config values in the directory ~/.ssh/config
// Other than host, it does not yet respect other config values set in the backy config file.
// It returns an ssh.Client used to run commands against.
func (remoteConfig *Host) ConnectToSSHHost(log *zerolog.Logger) (*ssh.Client, error) {
var sshClient *ssh.Client
var connectErr error
khPath := filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts")
f, _ := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "config"))
cfg, _ := ssh_config.Decode(f)
for _, host := range cfg.Hosts {
// var hostKey ssh.PublicKey
if host.Matches(remoteConfig.Host) {
var identityFile string
if remoteConfig.PrivateKeyPath == "" {
identityFile, _ = cfg.Get(remoteConfig.Host, "IdentityFile")
usr, _ := user.Current()
dir := usr.HomeDir
if identityFile == "~" {
// In case of "~", which won't be caught by the "else if"
identityFile = dir
} else if strings.HasPrefix(identityFile, "~/") {
// Use strings.HasPrefix so we don't match paths like
// "/something/~/something/"
identityFile = filepath.Join(dir, identityFile[2:])
}
remoteConfig.PrivateKeyPath = filepath.Join(identityFile)
log.Debug().Str("Private key path", remoteConfig.PrivateKeyPath).Send()
}
remoteConfig.HostName, _ = cfg.Get(remoteConfig.Host, "HostName")
remoteConfig.User, _ = cfg.Get(remoteConfig.Host, "User")
if remoteConfig.HostName == "" {
port, _ := cfg.Get(remoteConfig.Host, "Port")
if port == "" {
port = "22"
}
// remoteConfig.HostName[0] = remoteConfig.Host + ":" + port
} else {
// for index, hostName := range remoteConfig.HostName {
port, _ := cfg.Get(remoteConfig.Host, "Port")
if port == "" {
port = "22"
}
remoteConfig.HostName = remoteConfig.HostName + ":" + port
// remoteConfig.HostName[index] = hostName + ":" + port
}
// TODO: Add value/option to config for host key and add bool to check for host key
hostKeyCallback, err := knownhosts.New(khPath)
if err != nil {
log.Fatal().Err(err).Msg("could not create hostkeycallback function")
}
privateKey, err := os.ReadFile(remoteConfig.PrivateKeyPath)
if err != nil {
log.Fatal().Err(err).Msg("read private key error")
}
signer, err := ssh.ParsePrivateKey(privateKey)
if err != nil {
log.Fatal().Err(err).Msg("parse private key error")
}
sshConfig := &ssh.ClientConfig{
User: remoteConfig.User,
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
HostKeyCallback: hostKeyCallback,
// HostKeyAlgorithms: []string{ssh.KeyAlgoECDSA256},
}
// for _, host := range remoteConfig.HostName {
log.Info().Msgf("Connecting to host %s", remoteConfig.HostName)
sshClient, connectErr = ssh.Dial("tcp", remoteConfig.HostName, sshConfig)
if connectErr != nil {
log.Fatal().Str("host", remoteConfig.HostName).Err(connectErr).Send()
}
// }
break
}
}
return sshClient, connectErr
}