This repository has been archived on 2024-02-15. You can view files and clone it, but cannot push or open issues or pull requests.
config-mapper/internal/configuration/ssh.go

175 lines
3.8 KiB
Go

package configuration
import (
"bytes"
"errors"
"fmt"
"os"
osUser "os/user"
"strings"
"github.com/fatih/color"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
func loadConfigSSH(uri string) error {
config, host, path, err := getSSHConfig(uri)
if err != nil {
return err
}
c, err := ssh.Dial("tcp", host, config)
if err != nil {
return err
}
defer c.Close()
s, err := c.NewSession()
if err != nil {
return err
}
defer s.Close()
buff := new(bytes.Buffer)
s.Stdout = buff
if err := s.Run(fmt.Sprintf("cat %s", path)); err != nil {
return err
}
if err := viper.ReadConfig(buff); err != nil {
return err
}
return nil
}
func getSSHConfig(uriFlag string) (*ssh.ClientConfig, string, string, error) {
var err error
var user, passwd, host, configPath, key string
uri := strings.Split(uriFlag, "ssh://")[1]
if key = viper.GetString("ssh-key"); key != "" {
uri, user, passwd, err = getCredentials(uri)
if err != nil {
return nil, "", "", err
}
host, configPath, err = getUriContent(uri)
if err != nil {
return nil, "", "", err
}
} else if user = viper.GetString("ssh-user"); user != "" {
host, configPath, err = getUriContent(uri)
if err != nil {
return nil, "", "", err
}
passwd = viper.GetString("ssh-password")
} else {
uri, user, passwd, err = getCredentials(uri)
if err != nil {
return nil, "", "", err
}
if passwd == "" {
passwd = viper.GetString("ssh-password")
}
host, configPath, err = getUriContent(uri)
if err != nil {
return nil, "", "", err
}
}
if user == "" {
color.Yellow("WARNING: no user was found in either the URI and flags. Current user will be used")
var currentUser *osUser.User
currentUser, err = osUser.Current()
if err != nil {
return nil, "", "", err
}
user = currentUser.Username
}
var auth ssh.AuthMethod
if key != "" {
auth, err = createPubKeyAuth(key)
if err != nil {
return nil, "", "", err
}
} else {
auth = ssh.Password(passwd)
}
h, err := os.UserHomeDir()
if err != nil {
return nil, "", "", err
}
hostKeyCallback, err := knownhosts.New(fmt.Sprintf("%s/.ssh/known_hosts", h))
if err != nil {
return nil, "", "", err
}
if len(strings.SplitN(host, ":", 1)) == 1 {
host = fmt.Sprintf("%s:22", host)
}
return &ssh.ClientConfig{
User: user,
Auth: []ssh.AuthMethod{auth},
HostKeyCallback: hostKeyCallback,
}, host, configPath, nil
}
// getCredentials takes an SSH URI and returns (splitted URI, user, passwd, error)
//
// "passwd" can be empty in case of a single credential in URI
func getCredentials(uri string) (string, string, string, error) {
uriContent := strings.SplitN(uri, "@", 2)
if len(uriContent) == 1 {
fmt.Printf("uriContent: %v\n", uriContent)
return "", "", "", errors.New("no credentials in URI")
}
credentials := strings.SplitN(uriContent[0], ":", 2)
if len(credentials) == 1 {
return uriContent[1], credentials[0], "", nil
}
return uriContent[1], credentials[0], credentials[1], nil
}
// getUriContent takes an SSH URI and returns (host, path, error)
func getUriContent(uri string) (string, string, error) {
uriContent := strings.Split(uri, ":")
if len(uriContent) < 2 {
return "", "", errors.New("ssh URI is malformed. It's missing either a host or path. E.g: \"ssh://localhost:/my/config/file.yml\"")
}
return uriContent[0], uriContent[1], nil
}
func createPubKeyAuth(key string) (ssh.AuthMethod, error) {
var signer ssh.Signer
privateKey, err := os.ReadFile(key)
if err != nil {
return nil, err
}
if passphrase := os.Getenv("CONFIG_MAPPER_SSH_PASSPHRASE"); passphrase != "" {
signer, err = ssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(passphrase))
} else {
signer, err = ssh.ParsePrivateKey(privateKey)
}
if err != nil {
return nil, err
}
return ssh.PublicKeys(signer), nil
}