mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
* Add version check mechanism to gRPC calls
* Use semver not semver/v3
* Give dev builds a special "infinite" version number (999.0.0-dev)
* Handle no metadata on grpc call correctly for version check
* Move versioncheck package to root pkg/ from pkg/api/ since it is shared by both pkg/api/ and pkg/client/
* Only show "no daemon version" warning once
* Append version headers to metadata, not overwrite...
* Unit tests on versioncheck logic
* Move SetHeader to the ServerStream in ServerStreamInterceptor
* Go modernizer nits: interface{} -> any
* Use more conventional gRPC header names for version/min-versions
* Add TODO notes on checkDaemonVersionInResponse and related code that can be removed eventually after transition to version checking client/daemons
* Use testify for testing assertions
* Add explanatory comments on MinCLIVersion and MinDaemonVersion
---------
Co-authored-by: Pasha Sviderski <me@psviderski.name>
94 lines
3.0 KiB
Go
94 lines
3.0 KiB
Go
package connector
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"strconv"
|
|
|
|
"github.com/psviderski/uncloud/internal/cli/config"
|
|
"github.com/psviderski/uncloud/internal/machine/constants"
|
|
"github.com/psviderski/uncloud/internal/machine/network"
|
|
"github.com/psviderski/uncloud/internal/machine/network/tunnel"
|
|
"github.com/psviderski/uncloud/pkg/client"
|
|
"github.com/psviderski/uncloud/pkg/versioncheck"
|
|
"golang.org/x/net/proxy"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
)
|
|
|
|
// 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(constants.MachineAPIPort))
|
|
|
|
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.WithDefaultServiceConfig(defaultServiceConfig),
|
|
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
|
|
return c.tun.DialContext(ctx, "tcp", addr)
|
|
}),
|
|
grpc.WithUnaryInterceptor(versioncheck.ClientUnaryInterceptor),
|
|
grpc.WithStreamInterceptor(versioncheck.ClientStreamInterceptor),
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("connect to machine API through WireGuard tunnel: %w", err)
|
|
}
|
|
return conn, nil
|
|
}
|
|
|
|
func (c *WireGuardConnector) Dialer() (proxy.ContextDialer, error) {
|
|
return nil, fmt.Errorf("proxy connections not implemented for WireGuard connector")
|
|
}
|
|
|
|
func (c *WireGuardConnector) Close() error {
|
|
if c.tun != nil {
|
|
c.tun.Close()
|
|
c.tun = nil
|
|
}
|
|
return nil
|
|
}
|