From a64caf34a7b039da564f59a3395bcbd9af50ada8 Mon Sep 17 00:00:00 2001 From: Pavel Sviderski Date: Thu, 12 Sep 2024 09:55:02 +1000 Subject: [PATCH] add Token RPC endpoint to grab a machine token for adding it to a cluster --- Makefile | 6 +- internal/cli/cli.go | 20 ++- internal/cli/client/connector/ssh.go | 25 ++-- internal/machine/api/pb/machine.pb.go | 146 +++++++++++++++------ internal/machine/api/pb/machine.proto | 6 + internal/machine/api/pb/machine_grpc.pb.go | 39 ++++++ internal/machine/machine.go | 30 +++++ 7 files changed, 214 insertions(+), 58 deletions(-) diff --git a/Makefile b/Makefile index ba3c52fc..14dfd528 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,15 @@ .PHONY: build -uncloudd-dev1: +uncloud-dev: GOOS=linux GOARCH=amd64 go build -o uncloudd-linux-amd64 ./cmd/uncloudd && \ scp uncloudd-linux-amd64 spy@192.168.40.243:~/ && \ ssh spy@192.168.40.243 sudo install ./uncloudd-linux-amd64 /usr/local/bin/uncloudd + scp uncloudd-linux-amd64 spy@192.168.40.176:~/ && \ + ssh spy@192.168.40.176 sudo install ./uncloudd-linux-amd64 /usr/local/bin/uncloudd GOOS=linux GOARCH=amd64 go build -o uncloud-linux-amd64 ./cmd/uncloud && \ scp uncloud-linux-amd64 spy@192.168.40.243:~/ && \ ssh spy@192.168.40.243 sudo install ./uncloud-linux-amd64 /usr/local/bin/uncloud + scp uncloud-linux-amd64 spy@192.168.40.176:~/ && \ + ssh spy@192.168.40.176 sudo install ./uncloud-linux-amd64 /usr/local/bin/uncloud .PHONY: proto proto: diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 415b40bb..88e67cee 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "google.golang.org/protobuf/types/known/emptypb" "net/netip" "uncloud/internal/cli/client" "uncloud/internal/cli/client/connector" @@ -201,6 +202,7 @@ func (cli *CLI) AddMachine(ctx context.Context, remoteMachine RemoteMachine, clu _ = c.Close() }() + // Create a command executor and a machine API client over the SSH connection to the remote machine. sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath) if err != nil { return fmt.Errorf( @@ -210,12 +212,12 @@ func (cli *CLI) AddMachine(ctx context.Context, remoteMachine RemoteMachine, clu } machineExec := sshexec.NewRemote(sshClient) + conn := connector.NewSSHConnectorFromClient(sshClient) + machineClient, err := client.New(ctx, conn) + if err != nil { + return fmt.Errorf("connect to remote machine API: %w", err) + } // TODO: Check if the machine is already provisioned using machineClient and ask the user to reset it first. - //conn := connector.NewSSHConnectorFromClient(sshClient) - //machineClient, err := client.New(ctx, conn) - //if err != nil { - // return fmt.Errorf("connect to remote machine API: %w", err) - //} // TODO: Download and install the latest uncloudd binary by running the install shell script from GitHub. // For now upload the binary using scp manually. @@ -223,6 +225,14 @@ func (cli *CLI) AddMachine(ctx context.Context, remoteMachine RemoteMachine, clu return fmt.Errorf("uncloudd binary not found on the remote machine: %w", err) } + resp, err := machineClient.Token(ctx, &emptypb.Empty{}) + if err != nil { + return fmt.Errorf("get remote machine token: %w", err) + } + token := resp.Token + + fmt.Println("Token:", token) + //req := &pb.AddMachineRequest{ // Name: machineName, // Network: &pb.NetworkConfig{ diff --git a/internal/cli/client/connector/ssh.go b/internal/cli/client/connector/ssh.go index 55116ee4..d54cc255 100644 --- a/internal/cli/client/connector/ssh.go +++ b/internal/cli/client/connector/ssh.go @@ -28,17 +28,7 @@ type SSHConnector struct { } 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 + return &SSHConnector{config: *cfg} } func NewSSHConnectorFromClient(client *ssh.Client) *SSHConnector { @@ -59,8 +49,12 @@ func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) { } } + apiSockPath := c.config.APISockPath + if apiSockPath == "" { + apiSockPath = machine.DefaultAPISockPath + } conn, err := grpc.NewClient( - "unix://"+c.config.APISockPath, + "unix://"+apiSockPath, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer( func(ctx context.Context, addr string) (net.Conn, error) { @@ -68,9 +62,10 @@ func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) { 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 the Uncloud daemon running "+ - "on the remote machine and does the SSH user have permissions to access the socket?): %w", - addr, dErr, + "connect to machine API socket '%s' through SSH tunnel (is the Uncloud daemon 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 diff --git a/internal/machine/api/pb/machine.pb.go b/internal/machine/api/pb/machine.pb.go index 850000ec..0549bb6f 100644 --- a/internal/machine/api/pb/machine.pb.go +++ b/internal/machine/api/pb/machine.pb.go @@ -9,6 +9,7 @@ package pb import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" ) @@ -130,38 +131,93 @@ func (x *InitClusterResponse) GetMachine() *MachineInfo { return nil } +type TokenResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` +} + +func (x *TokenResponse) Reset() { + *x = TokenResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_internal_machine_api_pb_machine_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TokenResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenResponse) ProtoMessage() {} + +func (x *TokenResponse) ProtoReflect() protoreflect.Message { + mi := &file_internal_machine_api_pb_machine_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenResponse.ProtoReflect.Descriptor instead. +func (*TokenResponse) Descriptor() ([]byte, []int) { + return file_internal_machine_api_pb_machine_proto_rawDescGZIP(), []int{2} +} + +func (x *TokenResponse) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + var File_internal_machine_api_pb_machine_proto protoreflect.FileDescriptor var file_internal_machine_api_pb_machine_proto_rawDesc = []byte{ 0x0a, 0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x24, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x1a, 0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, - 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6c, 0x75, 0x73, - 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7e, 0x0a, 0x12, 0x49, 0x6e, 0x69, - 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x20, 0x0a, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x27, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, - 0x78, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x1d, 0x0a, 0x04, 0x75, 0x73, - 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x41, 0x0a, 0x13, 0x49, 0x6e, 0x69, - 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2a, 0x0a, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x32, 0x4b, 0x0a, 0x07, - 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x49, 0x6e, 0x69, 0x74, 0x43, - 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x69, - 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, - 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, - 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1b, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x70, 0x62, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x25, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x2f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x7e, 0x0a, 0x12, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, + 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, + 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x50, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x52, 0x07, + 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x1d, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x73, 0x65, 0x72, + 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x41, 0x0a, 0x13, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, + 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x07, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x22, 0x25, 0x0a, 0x0d, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x32, 0x80, 0x01, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x12, 0x40, 0x0a, 0x0b, + 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, + 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x70, 0x73, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x6b, 0x69, 0x2f, 0x75, 0x6e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x61, + 0x63, 0x68, 0x69, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -176,22 +232,26 @@ func file_internal_machine_api_pb_machine_proto_rawDescGZIP() []byte { return file_internal_machine_api_pb_machine_proto_rawDescData } -var file_internal_machine_api_pb_machine_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_internal_machine_api_pb_machine_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_internal_machine_api_pb_machine_proto_goTypes = []any{ (*InitClusterRequest)(nil), // 0: api.InitClusterRequest (*InitClusterResponse)(nil), // 1: api.InitClusterResponse - (*IPPrefix)(nil), // 2: api.IPPrefix - (*User)(nil), // 3: api.User - (*MachineInfo)(nil), // 4: api.MachineInfo + (*TokenResponse)(nil), // 2: api.TokenResponse + (*IPPrefix)(nil), // 3: api.IPPrefix + (*User)(nil), // 4: api.User + (*MachineInfo)(nil), // 5: api.MachineInfo + (*emptypb.Empty)(nil), // 6: google.protobuf.Empty } var file_internal_machine_api_pb_machine_proto_depIdxs = []int32{ - 2, // 0: api.InitClusterRequest.network:type_name -> api.IPPrefix - 3, // 1: api.InitClusterRequest.user:type_name -> api.User - 4, // 2: api.InitClusterResponse.machine:type_name -> api.MachineInfo + 3, // 0: api.InitClusterRequest.network:type_name -> api.IPPrefix + 4, // 1: api.InitClusterRequest.user:type_name -> api.User + 5, // 2: api.InitClusterResponse.machine:type_name -> api.MachineInfo 0, // 3: api.Machine.InitCluster:input_type -> api.InitClusterRequest - 1, // 4: api.Machine.InitCluster:output_type -> api.InitClusterResponse - 4, // [4:5] is the sub-list for method output_type - 3, // [3:4] is the sub-list for method input_type + 6, // 4: api.Machine.Token:input_type -> google.protobuf.Empty + 1, // 5: api.Machine.InitCluster:output_type -> api.InitClusterResponse + 2, // 6: api.Machine.Token:output_type -> api.TokenResponse + 5, // [5:7] is the sub-list for method output_type + 3, // [3:5] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -229,6 +289,18 @@ func file_internal_machine_api_pb_machine_proto_init() { return nil } } + file_internal_machine_api_pb_machine_proto_msgTypes[2].Exporter = func(v any, i int) any { + switch v := v.(*TokenResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -236,7 +308,7 @@ func file_internal_machine_api_pb_machine_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_internal_machine_api_pb_machine_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/machine/api/pb/machine.proto b/internal/machine/api/pb/machine.proto index b339131d..71151e08 100644 --- a/internal/machine/api/pb/machine.proto +++ b/internal/machine/api/pb/machine.proto @@ -4,11 +4,13 @@ package api; option go_package = "github.com/psviderski/uncloud/internal/machine/api/pb"; +import "google/protobuf/empty.proto"; import "internal/machine/api/pb/common.proto"; import "internal/machine/api/pb/cluster.proto"; service Machine { rpc InitCluster(InitClusterRequest) returns (InitClusterResponse); + rpc Token(google.protobuf.Empty) returns (TokenResponse); } message InitClusterRequest { @@ -20,3 +22,7 @@ message InitClusterRequest { message InitClusterResponse { MachineInfo machine = 1; } + +message TokenResponse { + string token = 1; +} diff --git a/internal/machine/api/pb/machine_grpc.pb.go b/internal/machine/api/pb/machine_grpc.pb.go index e67a5719..fce571ee 100644 --- a/internal/machine/api/pb/machine_grpc.pb.go +++ b/internal/machine/api/pb/machine_grpc.pb.go @@ -11,6 +11,7 @@ import ( grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" ) // This is a compile-time assertion to ensure that this generated file @@ -20,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( Machine_InitCluster_FullMethodName = "/api.Machine/InitCluster" + Machine_Token_FullMethodName = "/api.Machine/Token" ) // MachineClient is the client API for Machine service. @@ -27,6 +29,7 @@ const ( // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type MachineClient interface { InitCluster(ctx context.Context, in *InitClusterRequest, opts ...grpc.CallOption) (*InitClusterResponse, error) + Token(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TokenResponse, error) } type machineClient struct { @@ -47,11 +50,22 @@ func (c *machineClient) InitCluster(ctx context.Context, in *InitClusterRequest, return out, nil } +func (c *machineClient) Token(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TokenResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(TokenResponse) + err := c.cc.Invoke(ctx, Machine_Token_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // MachineServer is the server API for Machine service. // All implementations must embed UnimplementedMachineServer // for forward compatibility. type MachineServer interface { InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error) + Token(context.Context, *emptypb.Empty) (*TokenResponse, error) mustEmbedUnimplementedMachineServer() } @@ -65,6 +79,9 @@ type UnimplementedMachineServer struct{} func (UnimplementedMachineServer) InitCluster(context.Context, *InitClusterRequest) (*InitClusterResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method InitCluster not implemented") } +func (UnimplementedMachineServer) Token(context.Context, *emptypb.Empty) (*TokenResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Token not implemented") +} func (UnimplementedMachineServer) mustEmbedUnimplementedMachineServer() {} func (UnimplementedMachineServer) testEmbeddedByValue() {} @@ -104,6 +121,24 @@ func _Machine_InitCluster_Handler(srv interface{}, ctx context.Context, dec func return interceptor(ctx, in, info, handler) } +func _Machine_Token_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MachineServer).Token(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Machine_Token_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MachineServer).Token(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + // Machine_ServiceDesc is the grpc.ServiceDesc for Machine service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -115,6 +150,10 @@ var Machine_ServiceDesc = grpc.ServiceDesc{ MethodName: "InitCluster", Handler: _Machine_InitCluster_Handler, }, + { + MethodName: "Token", + Handler: _Machine_Token_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "internal/machine/api/pb/machine.proto", diff --git a/internal/machine/machine.go b/internal/machine/machine.go index a8b8eb17..97acae67 100644 --- a/internal/machine/machine.go +++ b/internal/machine/machine.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" "log/slog" "net" "net/netip" @@ -362,3 +363,32 @@ func (m *Machine) InitCluster(ctx context.Context, req *pb.InitClusterRequest) ( } return resp, nil } + +// Token returns the local machine's token that can be used for adding the machine to a cluster. +func (m *Machine) Token(_ context.Context, _ *emptypb.Empty) (*pb.TokenResponse, error) { + if len(m.state.Network.PublicKey) == 0 { + return nil, status.Error(codes.FailedPrecondition, "public key is not set in machine state") + } + + ips, err := network.ListRoutableIPs() + if err != nil { + return nil, status.Errorf(codes.Internal, "list routable IPs: %v", err) + } + publicIP, err := network.GetPublicIP() + // Ignore the error if failed to get the public IP using API services. + if err == nil { + ips = append(ips, publicIP) + } + endpoints := make([]netip.AddrPort, len(ips)) + for i, ip := range ips { + endpoints[i] = netip.AddrPortFrom(ip, network.WireGuardPort) + } + + token := NewToken(m.state.Network.PublicKey, endpoints) + tokenStr, err := token.String() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &pb.TokenResponse{Token: tokenStr}, nil +}