pkg/client/ssh/ssh.go (82 lines of code) (raw):
package ssh
import (
"fmt"
"io"
"net"
"github.com/go-logr/logr"
"github.com/pkg/errors"
"golang.org/x/crypto/ssh"
)
type SSHCommand struct {
Path string
Env []string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
type SSHClientInterface interface {
RunCommand(cmd *SSHCommand) ([]byte, error)
NewSession() (*ssh.Session, *ssh.Client, error)
}
type SSHClient struct {
Config *ssh.ClientConfig
Host string
Port int32
log logr.Logger
}
func (client *SSHClient) RunCommand(cmd *SSHCommand) (out []byte, err error) {
session, connection, err := client.NewSession()
if err != nil {
return nil, err
}
defer func() {
closeErr := session.Close()
if closeErr != nil && !errors.Is(closeErr, io.EOF) {
client.log.Error(closeErr, "failed to close SSH session")
}
closeErr = connection.Close()
if closeErr != nil && !errors.Is(closeErr, io.EOF) {
client.log.Error(closeErr, "failed to close SSH connection")
}
}()
out, err = session.Output(cmd.Path)
if err != nil {
return nil, fmt.Errorf("failed to exec cmd %q on the remote host, %w", cmd.Path, err)
}
return
}
func (client *SSHClient) NewSession() (*ssh.Session, *ssh.Client, error) {
addr := fmt.Sprintf("%s:%d", client.Host, client.Port)
connection, err := ssh.Dial("tcp", addr, client.Config)
if err != nil {
return nil, nil, fmt.Errorf("failed to create client connection to the SSH server: %s, %w", addr, err)
}
session, err := connection.NewSession()
if err != nil {
return nil, nil, fmt.Errorf("failed to create a new session for a ssh client, %w", err)
}
return session, connection, nil
}
func SshInit(userName string, privateKey []byte, host string, port int32, log logr.Logger) (SSHClient, error) {
signer, err := ssh.ParsePrivateKey(privateKey)
if err != nil {
return SSHClient{}, fmt.Errorf("failed to parse private key for user %q: %w", userName, err)
}
sshConfig := &ssh.ClientConfig{
User: userName,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(signer),
},
HostKeyCallback: ssh.HostKeyCallback(func(_ string, _ net.Addr, _ ssh.PublicKey) error {
return nil
}),
}
newClient := &SSHClient{
Config: sshConfig,
Host: host,
Port: port,
log: log,
}
log.Info("SSH Client has been initialized", "Username", userName, "host", host, "port", port)
return *newClient, nil
}