func (s *Shell) Run(cmd string) error {
	session, err := s.client.NewSession()
	if err != nil {
		return errors.Wrap(err, "error creating a session")
	}
	defer session.Close()
	return session.Run(cmd)
}
Run runs cmd on the remote host.
package sshutil
import (
	"io"
	"log"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"github.com/pkg/errors"
	"github.com/smallstep/cli/config"
	"golang.org/x/crypto/ssh"
	"golang.org/x/crypto/ssh/knownhosts"
	"golang.org/x/term"
)
func ProxyCommand(cmd, user, host, port string) string {
	cmd = strings.ReplaceAll(cmd, "%%", "%")
	cmd = strings.ReplaceAll(cmd, "%h", host)
	cmd = strings.ReplaceAll(cmd, "%p", port)
	return strings.ReplaceAll(cmd, "%r", user)
}
type ShellOption func(s *Shell) error
func WithAuthMethod(am ssh.AuthMethod) ShellOption {
	return func(s *Shell) error {
		s.authMethods = append(s.authMethods, am)
		return nil
	}
}
func WithSigner(signer ssh.Signer) ShellOption {
	return func(s *Shell) error {
		s.authMethods = append(s.authMethods, ssh.PublicKeys(signer))
		return nil
	}
}
func WithCertificate(cert *ssh.Certificate, priv interface{}) ShellOption {
	return func(s *Shell) error {
		signer, err := NewCertSigner(cert, priv)
		if err != nil {
			return err
		}
		s.authMethods = append(s.authMethods, ssh.PublicKeys(signer))
		return nil
	}
}
func WithAddUser(user string, cert *ssh.Certificate, priv interface{}) ShellOption {
	return func(s *Shell) error {
		return errors.New("not yet implemented")
	}
}
func WithBastion(user, address, command string) ShellOption {
	return func(s *Shell) error {
		s.dialer = func(callback ssh.HostKeyCallback) (*ssh.Client, error) {
			bastion, err := ssh.Dial("tcp", address, &ssh.ClientConfig{
				User:            user,
				Auth:            s.authMethods,
				HostKeyCallback: callback,
			})
			if err != nil {
				return nil, errors.Wrapf(err, "error connecting %s", address)
			}
			conn, err := bastion.Dial("tcp", s.address)
			if err != nil {
				return nil, errors.Wrapf(err, "error connecting %s", s.address)
			}
			c, chans, reqs, err := ssh.NewClientConn(conn, s.address, &ssh.ClientConfig{
				User:            s.user,
				Auth:            s.authMethods,
				HostKeyCallback: callback,
			})
			if err != nil {
				return nil, err
			}
			return ssh.NewClient(c, chans, reqs), nil
		}
		return nil
	}
}
func WithProxyCommand(command string) ShellOption {
	return func(s *Shell) error {
		s.dialer = func(callback ssh.HostKeyCallback) (*ssh.Client, error) {
			host, port, err := net.SplitHostPort(s.address)
			if err != nil {
				return nil, errors.Wrap(err, "error parsing address")
			}
			pr, pw := net.Pipe()
			args := strings.Fields(ProxyCommand(command, s.user, host, port))
			cmd := exec.Command(args[0], args[1:]...)
			cmd.Stdin = pw
			cmd.Stdout = pw
			cmd.Stderr = os.Stderr
			if err := cmd.Start(); err != nil {
				return nil, errors.Wrap(err, "error running proxy command")
			}
			c, chans, reqs, err := ssh.NewClientConn(pr, s.address, &ssh.ClientConfig{
				User:            s.user,
				Auth:            s.authMethods,
				HostKeyCallback: callback,
			})
			if err != nil {
				return nil, err
			}
			return ssh.NewClient(c, chans, reqs), nil
		}
		return nil
	}
}
func withDefaultAuthMethod() ShellOption {
	return func(s *Shell) error {
		agent, err := DialAgent()
		if err != nil {
			return err
		}
		s.authMethods = append(s.authMethods, agent.AuthMethod())
		return nil
	}
}
func withDefaultDialer(user, address string) ShellOption {
	return func(s *Shell) error {
		s.dialer = func(callback ssh.HostKeyCallback) (*ssh.Client, error) {
			client, err := ssh.Dial("tcp", address, &ssh.ClientConfig{
				User:            user,
				Auth:            s.authMethods,
				HostKeyCallback: callback,
			})
			if err != nil {
				return nil, errors.Wrapf(err, "error connecting %s", address)
			}
			return client, nil
		}
		return nil
	}
}
func formatAddress(address string) string {
	if _, _, err := net.SplitHostPort(address); err != nil {
		address += ":22"
	}
	return address
}
type Shell struct {
	user        string
	address     string
	authMethods []ssh.AuthMethod
	client      *ssh.Client
	dialer      func(callback ssh.HostKeyCallback) (*ssh.Client, error)
}
func NewShell(user, address string, opts ...ShellOption) (*Shell, error) {
	address = formatAddress(address)
	knownHosts, err := knownhosts.New(filepath.Join(config.Home(), ".ssh", "known_hosts"))
	if err != nil {
		return nil, errors.Wrap(err, "error reading known_hosts")
	}
	shell := &Shell{
		user:    user,
		address: address,
	}
	if err := shell.apply(opts); err != nil {
		return nil, err
	}
	if len(shell.authMethods) == 0 {
		if err := withDefaultAuthMethod()(shell); err != nil {
			return nil, err
		}
	}
	if shell.dialer == nil {
		if err := withDefaultDialer(user, address)(shell); err != nil {
			return nil, err
		}
	}
	if shell.client, err = shell.dialer(knownHosts); err != nil {
		return nil, err
	}
	return shell, nil
}
func (s *Shell) apply(opts []ShellOption) error {
	for _, fn := range opts {
		if err := fn(s); err != nil {
			return err
		}
	}
	return nil
}
func (s *Shell) Close() error {
	return s.client.Close()
}