Files
uncloud/pkg/client/connector/ssh.go
T

95 lines
2.4 KiB
Go

package connector
import (
"context"
"fmt"
"net"
"strings"
"github.com/psviderski/uncloud/internal/machine"
"github.com/psviderski/uncloud/internal/sshexec"
"golang.org/x/crypto/ssh"
"golang.org/x/net/proxy"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type SSHConnectorConfig struct {
User string
Host string
Port int
KeyPath string
SockPath string
}
// SSHConnector establishes a connection to the machine API through an SSH tunnel to the machine.
type SSHConnector struct {
config SSHConnectorConfig
client *ssh.Client
}
func NewSSHConnector(cfg *SSHConnectorConfig) *SSHConnector {
return &SSHConnector{config: *cfg}
}
func NewSSHConnectorFromClient(client *ssh.Client) *SSHConnector {
return &SSHConnector{client: client}
}
// TODO: handle context cancelation.
func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
if c.client == nil {
// Establish an SSH connection if the SSH client is not provided.
if c.config == (SSHConnectorConfig{}) {
return nil, fmt.Errorf("SSH connector not configured")
}
var err error
c.client, err = sshexec.Connect(c.config.User, c.config.Host, c.config.Port, c.config.KeyPath)
if err != nil {
return nil, fmt.Errorf("SSH login to %s@%s:%d: %w", c.config.User, c.config.Host, c.config.Port, err)
}
}
sockPath := c.config.SockPath
if sockPath == "" {
sockPath = machine.DefaultUncloudSockPath
}
conn, err := grpc.NewClient(
"unix://"+sockPath,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithContextDialer(
func(ctx context.Context, addr string) (net.Conn, error) {
addr = strings.TrimPrefix(addr, "unix://")
conn, dErr := c.client.DialContext(ctx, "unix", addr)
if dErr != nil {
return nil, fmt.Errorf(
"connect to machine API socket '%s' through SSH tunnel (is uncloud.service running "+
"on the remote machine and does the SSH user '%s' have permissions to access the socket?):"+
" %w",
addr, c.client.User(), dErr,
)
}
return conn, nil
},
),
)
if err != nil {
return nil, fmt.Errorf("create machine API client: %w", err)
}
return conn, nil
}
func (c *SSHConnector) Dialer() (proxy.ContextDialer, error) {
return nil, fmt.Errorf("dialer not implemented for SSHConnector yet")
}
func (c *SSHConnector) Close() error {
if c.client != nil {
err := c.client.Close()
c.client = nil
return err
}
return nil
}