mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 12:03:33 +00:00
implement CLI init cluster, add SSH and WireGuard cluster connectors
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net"
|
||||
"strings"
|
||||
"uncloud/internal/machine"
|
||||
"uncloud/internal/sshexec"
|
||||
)
|
||||
|
||||
type SSHConnectorConfig struct {
|
||||
User string
|
||||
Host string
|
||||
Port int
|
||||
KeyPath string
|
||||
|
||||
APISockPath 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 {
|
||||
c := &SSHConnector{config: *cfg}
|
||||
if c.config.User == "" {
|
||||
c.config.User = "root"
|
||||
}
|
||||
if c.config.Port == 0 {
|
||||
c.config.Port = 22
|
||||
}
|
||||
if c.config.APISockPath == "" {
|
||||
c.config.APISockPath = machine.DefaultAPISockPath
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// TODO: handle context cancelation.
|
||||
func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(
|
||||
"unix://"+c.config.APISockPath,
|
||||
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: %w", addr, dErr)
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create machine API client: %w", err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *SSHConnector) Close() error {
|
||||
if c.client != nil {
|
||||
err := c.client.Close()
|
||||
c.client = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package connector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"uncloud/internal/cli/client"
|
||||
"uncloud/internal/cli/config"
|
||||
machine2 "uncloud/internal/machine"
|
||||
"uncloud/internal/machine/network"
|
||||
"uncloud/internal/machine/network/tunnel"
|
||||
)
|
||||
|
||||
// WireGuardConnector establishes a connection to the cluster API through a WireGuard tunnel
|
||||
// to one of the cluster machines.
|
||||
type WireGuardConnector struct {
|
||||
user *client.User
|
||||
machines []config.MachineConnection
|
||||
tun *tunnel.Tunnel
|
||||
}
|
||||
|
||||
func NewWireGuardConnector(user *client.User, machines []config.MachineConnection) *WireGuardConnector {
|
||||
return &WireGuardConnector{
|
||||
user: user,
|
||||
machines: machines,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle context cancelation.
|
||||
func (c *WireGuardConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
|
||||
if len(c.machines) == 0 {
|
||||
return nil, fmt.Errorf("no machines to connect to")
|
||||
}
|
||||
// TODO: iterate over machines and try to connect to each one until successful.
|
||||
// For now, try to connect to only the first machine.
|
||||
machine := c.machines[0]
|
||||
endpointIPs, err := net.LookupIP(machine.Host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve IP for %q: %w", machine.Host, err)
|
||||
}
|
||||
endpointAddr, err := netip.ParseAddr(endpointIPs[0].String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse IP address %q: %w", endpointIPs[0].String(), err)
|
||||
}
|
||||
endpoint := netip.AddrPortFrom(endpointAddr, tunnel.DefaultEndpointPort)
|
||||
machineManagementIP := network.ManagementIP(machine.PublicKey)
|
||||
machineAPIAddr := net.JoinHostPort(machineManagementIP.String(), strconv.Itoa(machine2.APIPort))
|
||||
|
||||
tunCfg := &tunnel.Config{
|
||||
LocalAddress: c.user.ManagementIP(),
|
||||
LocalPrivateKey: c.user.PrivateKey(),
|
||||
RemotePublicKey: machine.PublicKey,
|
||||
RemoteNetwork: netip.PrefixFrom(machineManagementIP, 128),
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
if c.tun, err = tunnel.Connect(tunCfg); err != nil {
|
||||
return nil, fmt.Errorf("establish WireGuard tunnel to %q: %w", endpoint, err)
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(
|
||||
machineAPIAddr,
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
|
||||
return c.tun.DialContext(ctx, "tcp", addr)
|
||||
}),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect to machine API through WireGuard tunnel: %w", err)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *WireGuardConnector) Close() error {
|
||||
if c.tun != nil {
|
||||
c.tun.Close()
|
||||
c.tun = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user