diff --git a/iface/iface.go b/iface/iface.go index 4eb40a026..16504251b 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -8,7 +8,7 @@ import ( ) const ( - DefaultMTU = 1280 + DefaultMTU = 1280 DefaultWgPort = 51820 ) diff --git a/management/client/client.go b/management/client/client.go index ec56b5214..10246204a 100644 --- a/management/client/client.go +++ b/management/client/client.go @@ -14,4 +14,5 @@ type Client interface { GetServerPublicKey() (*wgtypes.Key, error) Register(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error) Login(serverKey wgtypes.Key) (*proto.LoginResponse, error) + GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) } diff --git a/management/client/client_test.go b/management/client/client_test.go index 4fb406573..2e83d8988 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -25,25 +25,17 @@ import ( "google.golang.org/grpc/status" ) -var tested *GrpcClient -var serverAddr string -var mgmtMockServer *mock_server.ManagementServiceServerMock -var serverKey wgtypes.Key - const ValidKey = "A2C8E62B-38F5-4553-B31E-DD66C696CEBB" -func Test_Start(t *testing.T) { +func startManagement(t *testing.T) (*grpc.Server, net.Listener) { + level, _ := log.ParseLevel("debug") log.SetLevel(level) - testKey, err := wgtypes.GenerateKey() - if err != nil { - t.Fatal(err) - } testDir := t.TempDir() - ctx := context.Background() + config := &mgmt.Config{} - _, err = util.ReadJson("../server/testdata/management.json", config) + _, err := util.ReadJson("../server/testdata/management.json", config) if err != nil { t.Fatal(err) } @@ -52,15 +44,7 @@ func Test_Start(t *testing.T) { if err != nil { t.Fatal(err) } - _, listener := startManagement(config, t) - serverAddr = listener.Addr().String() - tested, err = NewClient(ctx, serverAddr, testKey, false) - if err != nil { - t.Fatal(err) - } -} -func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Listener) { lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) @@ -89,7 +73,7 @@ func startManagement(config *mgmt.Config, t *testing.T) (*grpc.Server, net.Liste return s, lis } -func startMockManagement(t *testing.T) (*grpc.Server, net.Listener) { +func startMockManagement(t *testing.T) (*grpc.Server, net.Listener, *mock_server.ManagementServiceServerMock, wgtypes.Key) { lis, err := net.Listen("tcp", ":0") if err != nil { t.Fatal(err) @@ -97,12 +81,12 @@ func startMockManagement(t *testing.T) (*grpc.Server, net.Listener) { s := grpc.NewServer() - serverKey, err = wgtypes.GenerateKey() + serverKey, err := wgtypes.GenerateKey() if err != nil { t.Fatal(err) } - mgmtMockServer = &mock_server.ManagementServiceServerMock{ + mgmtMockServer := &mock_server.ManagementServiceServerMock{ GetServerKeyFunc: func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error) { response := &proto.ServerKeyResponse{ Key: serverKey.PublicKey().String(), @@ -119,27 +103,59 @@ func startMockManagement(t *testing.T) (*grpc.Server, net.Listener) { } }() - return s, lis + return s, lis, mgmtMockServer, serverKey +} + +func closeManagementSilently(s *grpc.Server, listener net.Listener) { + s.GracefulStop() + err := listener.Close() + if err != nil { + log.Warnf("error while closing management listener %v", err) + return + } } func TestClient_GetServerPublicKey(t *testing.T) { - - key, err := tested.GetServerPublicKey() + testKey, err := wgtypes.GenerateKey() if err != nil { - t.Error(err) + t.Fatal(err) + } + ctx := context.Background() + s, listener := startManagement(t) + defer closeManagementSilently(s, listener) + + client, err := NewClient(ctx, listener.Addr().String(), testKey, false) + if err != nil { + t.Fatal(err) } + key, err := client.GetServerPublicKey() + if err != nil { + t.Error("couldn't retrieve management public key") + } if key == nil { - t.Error("expecting non nil server key got nil") + t.Error("got an empty management public key") } } func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) { - key, err := tested.GetServerPublicKey() + testKey, err := wgtypes.GenerateKey() if err != nil { t.Fatal(err) } - _, err = tested.Login(*key) + ctx := context.Background() + s, listener := startManagement(t) + defer closeManagementSilently(s, listener) + + client, err := NewClient(ctx, listener.Addr().String(), testKey, false) + if err != nil { + t.Fatal(err) + } + key, err := client.GetServerPublicKey() + if err != nil { + t.Fatal(err) + } + _, err = client.Login(*key) if err == nil { t.Error("expecting err on unregistered login, got nil") } @@ -149,12 +165,25 @@ func TestClient_LoginUnregistered_ShouldThrow_401(t *testing.T) { } func TestClient_LoginRegistered(t *testing.T) { - key, err := tested.GetServerPublicKey() + testKey, err := wgtypes.GenerateKey() + if err != nil { + t.Fatal(err) + } + ctx := context.Background() + s, listener := startManagement(t) + defer closeManagementSilently(s, listener) + + client, err := NewClient(ctx, listener.Addr().String(), testKey, false) + if err != nil { + t.Fatal(err) + } + + key, err := client.GetServerPublicKey() if err != nil { t.Error(err) } info := system.GetInfo() - resp, err := tested.Register(*key, ValidKey, "", info) + resp, err := client.Register(*key, ValidKey, "", info) if err != nil { t.Error(err) } @@ -165,13 +194,26 @@ func TestClient_LoginRegistered(t *testing.T) { } func TestClient_Sync(t *testing.T) { - serverKey, err := tested.GetServerPublicKey() + testKey, err := wgtypes.GenerateKey() + if err != nil { + t.Fatal(err) + } + ctx := context.Background() + s, listener := startManagement(t) + defer closeManagementSilently(s, listener) + + client, err := NewClient(ctx, listener.Addr().String(), testKey, false) + if err != nil { + t.Fatal(err) + } + + serverKey, err := client.GetServerPublicKey() if err != nil { t.Error(err) } info := system.GetInfo() - _, err = tested.Register(*serverKey, ValidKey, "", info) + _, err = client.Register(*serverKey, ValidKey, "", info) if err != nil { t.Error(err) } @@ -181,7 +223,7 @@ func TestClient_Sync(t *testing.T) { if err != nil { t.Error(err) } - remoteClient, err := NewClient(context.TODO(), serverAddr, remoteKey, false) + remoteClient, err := NewClient(context.TODO(), listener.Addr().String(), remoteKey, false) if err != nil { t.Fatal(err) } @@ -195,7 +237,7 @@ func TestClient_Sync(t *testing.T) { ch := make(chan *mgmtProto.SyncResponse, 1) go func() { - err = tested.Sync(func(msg *mgmtProto.SyncResponse) error { + err = client.Sync(func(msg *mgmtProto.SyncResponse) error { ch <- msg return nil }) @@ -227,7 +269,8 @@ func TestClient_Sync(t *testing.T) { } func Test_SystemMetaDataFromClient(t *testing.T) { - _, lis := startMockManagement(t) + s, lis, mgmtMockServer, serverKey := startMockManagement(t) + defer s.GracefulStop() testKey, err := wgtypes.GenerateKey() if err != nil { @@ -304,3 +347,49 @@ func Test_SystemMetaDataFromClient(t *testing.T) { assert.Equal(t, ValidKey, actualValidKey) assert.Equal(t, expectedMeta, actualMeta) } + +func Test_GetDeviceAuthorizationFlow(t *testing.T) { + s, lis, mgmtMockServer, serverKey := startMockManagement(t) + defer s.GracefulStop() + + testKey, err := wgtypes.GenerateKey() + if err != nil { + log.Fatal(err) + } + + serverAddr := lis.Addr().String() + ctx := context.Background() + + client, err := NewClient(ctx, serverAddr, testKey, false) + if err != nil { + log.Fatalf("error while creating testClient: %v", err) + } + + expectedFlowInfo := &proto.DeviceAuthorizationFlow{ + Provider: 0, + ProviderConfig: &proto.ProviderConfig{ClientID: "client"}, + } + + mgmtMockServer.GetDeviceAuthorizationFlowFunc = + func(ctx context.Context, req *mgmtProto.EncryptedMessage) (*proto.EncryptedMessage, error) { + + encryptedResp, err := encryption.EncryptMessage(serverKey, client.key, expectedFlowInfo) + if err != nil { + return nil, err + } + + return &mgmtProto.EncryptedMessage{ + WgPubKey: serverKey.PublicKey().String(), + Body: encryptedResp, + Version: 0, + }, nil + } + + flowInfo, err := client.GetDeviceAuthorizationFlow(serverKey) + if err != nil { + t.Error("error while retrieving device auth flow information") + } + + assert.Equal(t, expectedFlowInfo.Provider, flowInfo.Provider, "provider should match") + assert.Equal(t, expectedFlowInfo.ProviderConfig.ClientID, flowInfo.ProviderConfig.ClientID, "provider configured client ID should match") +} diff --git a/management/client/grpc.go b/management/client/grpc.go index 06bf4e09a..5fbd29290 100644 --- a/management/client/grpc.go +++ b/management/client/grpc.go @@ -245,3 +245,37 @@ func (c *GrpcClient) Register(serverKey wgtypes.Key, setupKey string, jwtToken s func (c *GrpcClient) Login(serverKey wgtypes.Key) (*proto.LoginResponse, error) { return c.login(serverKey, &proto.LoginRequest{}) } + +// GetDeviceAuthorizationFlow returns a device authorization flow information. +// It also takes care of encrypting and decrypting messages. +func (c *GrpcClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) { + if !c.ready() { + return nil, fmt.Errorf("no connection to management in order to get device authorization flow") + } + mgmCtx, cancel := context.WithTimeout(c.ctx, time.Second*2) + defer cancel() + + message := &proto.DeviceAuthorizationFlowRequest{} + encryptedMSG, err := encryption.EncryptMessage(serverKey, c.key, message) + if err != nil { + return nil, err + } + + resp, err := c.realClient.GetDeviceAuthorizationFlow(mgmCtx, &proto.EncryptedMessage{ + WgPubKey: c.key.PublicKey().String(), + Body: encryptedMSG}, + ) + if err != nil { + return nil, err + } + + flowInfoResp := &proto.DeviceAuthorizationFlow{} + err = encryption.DecryptMessage(serverKey, c.key, resp.Body, flowInfoResp) + if err != nil { + errWithMSG := fmt.Errorf("failed to decrypt device authorization flow message: %s", err) + log.Error(errWithMSG) + return nil, errWithMSG + } + + return flowInfoResp, nil +} diff --git a/management/client/mock.go b/management/client/mock.go index be6025632..3e2a3c075 100644 --- a/management/client/mock.go +++ b/management/client/mock.go @@ -7,11 +7,12 @@ import ( ) type MockClient struct { - CloseFunc func() error - SyncFunc func(msgHandler func(msg *proto.SyncResponse) error) error - GetServerPublicKeyFunc func() (*wgtypes.Key, error) - RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error) - LoginFunc func(serverKey wgtypes.Key) (*proto.LoginResponse, error) + CloseFunc func() error + SyncFunc func(msgHandler func(msg *proto.SyncResponse) error) error + GetServerPublicKeyFunc func() (*wgtypes.Key, error) + RegisterFunc func(serverKey wgtypes.Key, setupKey string, jwtToken string, info *system.Info) (*proto.LoginResponse, error) + LoginFunc func(serverKey wgtypes.Key) (*proto.LoginResponse, error) + GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) } func (m *MockClient) Close() error { @@ -48,3 +49,10 @@ func (m *MockClient) Login(serverKey wgtypes.Key) (*proto.LoginResponse, error) } return m.LoginFunc(serverKey) } + +func (m *MockClient) GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error) { + if m.GetDeviceAuthorizationFlowFunc == nil { + return nil, nil + } + return m.GetDeviceAuthorizationFlowFunc(serverKey) +} diff --git a/management/proto/management.pb.go b/management/proto/management.pb.go index 61465bc37..1f5bda208 100644 --- a/management/proto/management.pb.go +++ b/management/proto/management.pb.go @@ -76,6 +76,49 @@ func (HostConfig_Protocol) EnumDescriptor() ([]byte, []int) { return file_management_proto_rawDescGZIP(), []int{9, 0} } +type DeviceAuthorizationFlowProvider int32 + +const ( + DeviceAuthorizationFlow_HOSTED DeviceAuthorizationFlowProvider = 0 +) + +// Enum value maps for DeviceAuthorizationFlowProvider. +var ( + DeviceAuthorizationFlowProvider_name = map[int32]string{ + 0: "HOSTED", + } + DeviceAuthorizationFlowProvider_value = map[string]int32{ + "HOSTED": 0, + } +) + +func (x DeviceAuthorizationFlowProvider) Enum() *DeviceAuthorizationFlowProvider { + p := new(DeviceAuthorizationFlowProvider) + *p = x + return p +} + +func (x DeviceAuthorizationFlowProvider) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DeviceAuthorizationFlowProvider) Descriptor() protoreflect.EnumDescriptor { + return file_management_proto_enumTypes[1].Descriptor() +} + +func (DeviceAuthorizationFlowProvider) Type() protoreflect.EnumType { + return &file_management_proto_enumTypes[1] +} + +func (x DeviceAuthorizationFlowProvider) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DeviceAuthorizationFlowProvider.Descriptor instead. +func (DeviceAuthorizationFlowProvider) EnumDescriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{15, 0} +} + type EncryptedMessage struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -973,6 +1016,180 @@ func (x *RemotePeerConfig) GetAllowedIps() []string { return nil } +// DeviceAuthorizationFlowRequest empty struct for future expansion +type DeviceAuthorizationFlowRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeviceAuthorizationFlowRequest) Reset() { + *x = DeviceAuthorizationFlowRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeviceAuthorizationFlowRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeviceAuthorizationFlowRequest) ProtoMessage() {} + +func (x *DeviceAuthorizationFlowRequest) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[14] + 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 DeviceAuthorizationFlowRequest.ProtoReflect.Descriptor instead. +func (*DeviceAuthorizationFlowRequest) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{14} +} + +// DeviceAuthorizationFlow represents Device Authorization Flow information +// that can be used by the client to login initiate a Oauth 2.0 device authorization grant flow +// see https://datatracker.ietf.org/doc/html/rfc8628 +type DeviceAuthorizationFlow struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // An IDP provider , (eg. Auth0) + Provider DeviceAuthorizationFlowProvider `protobuf:"varint,1,opt,name=Provider,proto3,enum=management.DeviceAuthorizationFlowProvider" json:"Provider,omitempty"` + ProviderConfig *ProviderConfig `protobuf:"bytes,2,opt,name=ProviderConfig,proto3" json:"ProviderConfig,omitempty"` +} + +func (x *DeviceAuthorizationFlow) Reset() { + *x = DeviceAuthorizationFlow{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeviceAuthorizationFlow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeviceAuthorizationFlow) ProtoMessage() {} + +func (x *DeviceAuthorizationFlow) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[15] + 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 DeviceAuthorizationFlow.ProtoReflect.Descriptor instead. +func (*DeviceAuthorizationFlow) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{15} +} + +func (x *DeviceAuthorizationFlow) GetProvider() DeviceAuthorizationFlowProvider { + if x != nil { + return x.Provider + } + return DeviceAuthorizationFlow_HOSTED +} + +func (x *DeviceAuthorizationFlow) GetProviderConfig() *ProviderConfig { + if x != nil { + return x.ProviderConfig + } + return nil +} + +// ProviderConfig has all attributes needed to initiate a device authorization flow +type ProviderConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // An IDP application client id + ClientID string `protobuf:"bytes,1,opt,name=ClientID,proto3" json:"ClientID,omitempty"` + // An IDP application client secret + ClientSecret string `protobuf:"bytes,2,opt,name=ClientSecret,proto3" json:"ClientSecret,omitempty"` + // An IDP API domain + Domain string `protobuf:"bytes,3,opt,name=Domain,proto3" json:"Domain,omitempty"` + // An Audience for validation + Audience string `protobuf:"bytes,4,opt,name=Audience,proto3" json:"Audience,omitempty"` +} + +func (x *ProviderConfig) Reset() { + *x = ProviderConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_management_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ProviderConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderConfig) ProtoMessage() {} + +func (x *ProviderConfig) ProtoReflect() protoreflect.Message { + mi := &file_management_proto_msgTypes[16] + 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 ProviderConfig.ProtoReflect.Descriptor instead. +func (*ProviderConfig) Descriptor() ([]byte, []int) { + return file_management_proto_rawDescGZIP(), []int{16} +} + +func (x *ProviderConfig) GetClientID() string { + if x != nil { + return x.ClientID + } + return "" +} + +func (x *ProviderConfig) GetClientSecret() string { + if x != nil { + return x.ClientSecret + } + return "" +} + +func (x *ProviderConfig) GetDomain() string { + if x != nil { + return x.Domain + } + return "" +} + +func (x *ProviderConfig) GetAudience() string { + if x != nil { + return x.Audience + } + return "" +} + var File_management_proto protoreflect.FileDescriptor var file_management_proto_rawDesc = []byte{ @@ -1094,26 +1311,54 @@ var file_management_proto_rawDesc = []byte{ 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, - 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x32, 0x9b, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, - 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, - 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, + 0x77, 0x65, 0x64, 0x49, 0x70, 0x73, 0x22, 0x20, 0x0a, 0x1e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, + 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xbf, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x76, + 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x52, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x42, + 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x22, 0x16, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0a, + 0x0a, 0x06, 0x48, 0x4f, 0x53, 0x54, 0x45, 0x44, 0x10, 0x00, 0x22, 0x84, 0x01, 0x0a, 0x0e, 0x50, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, + 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x41, 0x75, 0x64, 0x69, 0x65, 0x6e, 0x63, + 0x65, 0x32, 0xf7, 0x02, 0x0a, 0x11, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, + 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x46, + 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, + 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x69, 0x73, + 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x11, 0x2e, 0x6d, 0x61, 0x6e, + 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, + 0x5a, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6c, 0x6f, 0x77, 0x12, 0x1c, 0x2e, + 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x1c, 0x2e, 0x6d, 0x61, 0x6e, 0x61, - 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0c, 0x47, - 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x12, 0x11, 0x2e, 0x6d, 0x61, - 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, - 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x33, 0x0a, 0x09, 0x69, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x11, 0x2e, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x11, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1128,55 +1373,63 @@ func file_management_proto_rawDescGZIP() []byte { return file_management_proto_rawDescData } -var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_management_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_management_proto_msgTypes = make([]protoimpl.MessageInfo, 17) var file_management_proto_goTypes = []interface{}{ - (HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol - (*EncryptedMessage)(nil), // 1: management.EncryptedMessage - (*SyncRequest)(nil), // 2: management.SyncRequest - (*SyncResponse)(nil), // 3: management.SyncResponse - (*LoginRequest)(nil), // 4: management.LoginRequest - (*PeerSystemMeta)(nil), // 5: management.PeerSystemMeta - (*LoginResponse)(nil), // 6: management.LoginResponse - (*ServerKeyResponse)(nil), // 7: management.ServerKeyResponse - (*Empty)(nil), // 8: management.Empty - (*WiretrusteeConfig)(nil), // 9: management.WiretrusteeConfig - (*HostConfig)(nil), // 10: management.HostConfig - (*ProtectedHostConfig)(nil), // 11: management.ProtectedHostConfig - (*PeerConfig)(nil), // 12: management.PeerConfig - (*NetworkMap)(nil), // 13: management.NetworkMap - (*RemotePeerConfig)(nil), // 14: management.RemotePeerConfig - (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp + (HostConfig_Protocol)(0), // 0: management.HostConfig.Protocol + (DeviceAuthorizationFlowProvider)(0), // 1: management.DeviceAuthorizationFlow.provider + (*EncryptedMessage)(nil), // 2: management.EncryptedMessage + (*SyncRequest)(nil), // 3: management.SyncRequest + (*SyncResponse)(nil), // 4: management.SyncResponse + (*LoginRequest)(nil), // 5: management.LoginRequest + (*PeerSystemMeta)(nil), // 6: management.PeerSystemMeta + (*LoginResponse)(nil), // 7: management.LoginResponse + (*ServerKeyResponse)(nil), // 8: management.ServerKeyResponse + (*Empty)(nil), // 9: management.Empty + (*WiretrusteeConfig)(nil), // 10: management.WiretrusteeConfig + (*HostConfig)(nil), // 11: management.HostConfig + (*ProtectedHostConfig)(nil), // 12: management.ProtectedHostConfig + (*PeerConfig)(nil), // 13: management.PeerConfig + (*NetworkMap)(nil), // 14: management.NetworkMap + (*RemotePeerConfig)(nil), // 15: management.RemotePeerConfig + (*DeviceAuthorizationFlowRequest)(nil), // 16: management.DeviceAuthorizationFlowRequest + (*DeviceAuthorizationFlow)(nil), // 17: management.DeviceAuthorizationFlow + (*ProviderConfig)(nil), // 18: management.ProviderConfig + (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp } var file_management_proto_depIdxs = []int32{ - 9, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig - 12, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig - 14, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig - 13, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap - 5, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta - 9, // 5: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig - 12, // 6: management.LoginResponse.peerConfig:type_name -> management.PeerConfig - 15, // 7: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp - 10, // 8: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig - 11, // 9: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig - 10, // 10: management.WiretrusteeConfig.signal:type_name -> management.HostConfig + 10, // 0: management.SyncResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig + 13, // 1: management.SyncResponse.peerConfig:type_name -> management.PeerConfig + 15, // 2: management.SyncResponse.remotePeers:type_name -> management.RemotePeerConfig + 14, // 3: management.SyncResponse.NetworkMap:type_name -> management.NetworkMap + 6, // 4: management.LoginRequest.meta:type_name -> management.PeerSystemMeta + 10, // 5: management.LoginResponse.wiretrusteeConfig:type_name -> management.WiretrusteeConfig + 13, // 6: management.LoginResponse.peerConfig:type_name -> management.PeerConfig + 19, // 7: management.ServerKeyResponse.expiresAt:type_name -> google.protobuf.Timestamp + 11, // 8: management.WiretrusteeConfig.stuns:type_name -> management.HostConfig + 12, // 9: management.WiretrusteeConfig.turns:type_name -> management.ProtectedHostConfig + 11, // 10: management.WiretrusteeConfig.signal:type_name -> management.HostConfig 0, // 11: management.HostConfig.protocol:type_name -> management.HostConfig.Protocol - 10, // 12: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig - 12, // 13: management.NetworkMap.peerConfig:type_name -> management.PeerConfig - 14, // 14: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig - 1, // 15: management.ManagementService.Login:input_type -> management.EncryptedMessage - 1, // 16: management.ManagementService.Sync:input_type -> management.EncryptedMessage - 8, // 17: management.ManagementService.GetServerKey:input_type -> management.Empty - 8, // 18: management.ManagementService.isHealthy:input_type -> management.Empty - 1, // 19: management.ManagementService.Login:output_type -> management.EncryptedMessage - 1, // 20: management.ManagementService.Sync:output_type -> management.EncryptedMessage - 7, // 21: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse - 8, // 22: management.ManagementService.isHealthy:output_type -> management.Empty - 19, // [19:23] is the sub-list for method output_type - 15, // [15:19] is the sub-list for method input_type - 15, // [15:15] is the sub-list for extension type_name - 15, // [15:15] is the sub-list for extension extendee - 0, // [0:15] is the sub-list for field type_name + 11, // 12: management.ProtectedHostConfig.hostConfig:type_name -> management.HostConfig + 13, // 13: management.NetworkMap.peerConfig:type_name -> management.PeerConfig + 15, // 14: management.NetworkMap.remotePeers:type_name -> management.RemotePeerConfig + 1, // 15: management.DeviceAuthorizationFlow.Provider:type_name -> management.DeviceAuthorizationFlow.provider + 18, // 16: management.DeviceAuthorizationFlow.ProviderConfig:type_name -> management.ProviderConfig + 2, // 17: management.ManagementService.Login:input_type -> management.EncryptedMessage + 2, // 18: management.ManagementService.Sync:input_type -> management.EncryptedMessage + 9, // 19: management.ManagementService.GetServerKey:input_type -> management.Empty + 9, // 20: management.ManagementService.isHealthy:input_type -> management.Empty + 2, // 21: management.ManagementService.GetDeviceAuthorizationFlow:input_type -> management.EncryptedMessage + 2, // 22: management.ManagementService.Login:output_type -> management.EncryptedMessage + 2, // 23: management.ManagementService.Sync:output_type -> management.EncryptedMessage + 8, // 24: management.ManagementService.GetServerKey:output_type -> management.ServerKeyResponse + 9, // 25: management.ManagementService.isHealthy:output_type -> management.Empty + 2, // 26: management.ManagementService.GetDeviceAuthorizationFlow:output_type -> management.EncryptedMessage + 22, // [22:27] is the sub-list for method output_type + 17, // [17:22] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_management_proto_init() } @@ -1353,14 +1606,50 @@ func file_management_proto_init() { return nil } } + file_management_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeviceAuthorizationFlowRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeviceAuthorizationFlow); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_management_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProviderConfig); 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{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_management_proto_rawDesc, - NumEnums: 1, - NumMessages: 14, + NumEnums: 2, + NumMessages: 17, NumExtensions: 0, NumServices: 1, }, diff --git a/management/proto/management.proto b/management/proto/management.proto index 14ef4fa86..966b769ce 100644 --- a/management/proto/management.proto +++ b/management/proto/management.proto @@ -24,6 +24,13 @@ service ManagementService { // health check endpoint rpc isHealthy(Empty) returns (Empty) {} + + // Exposes a device authorization flow information + // This is used for initiating a Oauth 2 device authorization grant flow + // which will be used by our clients to Login. + // EncryptedMessage of the request has a body of DeviceAuthorizationFlowRequest. + // EncryptedMessage of the response has a body of DeviceAuthorizationFlow. + rpc GetDeviceAuthorizationFlow(EncryptedMessage) returns (EncryptedMessage) {} } message EncryptedMessage { @@ -164,4 +171,30 @@ message RemotePeerConfig { // Wireguard allowed IPs of a remote peer e.g. [10.30.30.1/32] repeated string allowedIps = 2; +} +// DeviceAuthorizationFlowRequest empty struct for future expansion +message DeviceAuthorizationFlowRequest {} +// DeviceAuthorizationFlow represents Device Authorization Flow information +// that can be used by the client to login initiate a Oauth 2.0 device authorization grant flow +// see https://datatracker.ietf.org/doc/html/rfc8628 +message DeviceAuthorizationFlow { + // An IDP provider , (eg. Auth0) + provider Provider = 1; + ProviderConfig ProviderConfig = 2; + + enum provider { + HOSTED = 0; + } +} + +// ProviderConfig has all attributes needed to initiate a device authorization flow +message ProviderConfig { + // An IDP application client id + string ClientID = 1; + // An IDP application client secret + string ClientSecret = 2; + // An IDP API domain + string Domain =3; + // An Audience for validation + string Audience = 4; } \ No newline at end of file diff --git a/management/proto/management_grpc.pb.go b/management/proto/management_grpc.pb.go index 8c285c326..5db4810a4 100644 --- a/management/proto/management_grpc.pb.go +++ b/management/proto/management_grpc.pb.go @@ -31,6 +31,10 @@ type ManagementServiceClient interface { GetServerKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ServerKeyResponse, error) // health check endpoint IsHealthy(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) + // Exposes a device authorization flow information + // This is used for initiating a Oauth 2 device authorization grant flow + // which will be used by our clients to Login + GetDeviceAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) } type managementServiceClient struct { @@ -100,6 +104,15 @@ func (c *managementServiceClient) IsHealthy(ctx context.Context, in *Empty, opts return out, nil } +func (c *managementServiceClient) GetDeviceAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { + out := new(EncryptedMessage) + err := c.cc.Invoke(ctx, "/management.ManagementService/GetDeviceAuthorizationFlow", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // ManagementServiceServer is the server API for ManagementService service. // All implementations must embed UnimplementedManagementServiceServer // for forward compatibility @@ -117,6 +130,10 @@ type ManagementServiceServer interface { GetServerKey(context.Context, *Empty) (*ServerKeyResponse, error) // health check endpoint IsHealthy(context.Context, *Empty) (*Empty, error) + // Exposes a device authorization flow information + // This is used for initiating a Oauth 2 device authorization grant flow + // which will be used by our clients to Login + GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) mustEmbedUnimplementedManagementServiceServer() } @@ -136,6 +153,9 @@ func (UnimplementedManagementServiceServer) GetServerKey(context.Context, *Empty func (UnimplementedManagementServiceServer) IsHealthy(context.Context, *Empty) (*Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method IsHealthy not implemented") } +func (UnimplementedManagementServiceServer) GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetDeviceAuthorizationFlow not implemented") +} func (UnimplementedManagementServiceServer) mustEmbedUnimplementedManagementServiceServer() {} // UnsafeManagementServiceServer may be embedded to opt out of forward compatibility for this service. @@ -224,6 +244,24 @@ func _ManagementService_IsHealthy_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _ManagementService_GetDeviceAuthorizationFlow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EncryptedMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ManagementServiceServer).GetDeviceAuthorizationFlow(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/management.ManagementService/GetDeviceAuthorizationFlow", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ManagementServiceServer).GetDeviceAuthorizationFlow(ctx, req.(*EncryptedMessage)) + } + return interceptor(ctx, in, info, handler) +} + // ManagementService_ServiceDesc is the grpc.ServiceDesc for ManagementService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -243,6 +281,10 @@ var ManagementService_ServiceDesc = grpc.ServiceDesc{ MethodName: "isHealthy", Handler: _ManagementService_IsHealthy_Handler, }, + { + MethodName: "GetDeviceAuthorizationFlow", + Handler: _ManagementService_GetDeviceAuthorizationFlow_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/management/server/config.go b/management/server/config.go index 5159efea5..7a3948c2e 100644 --- a/management/server/config.go +++ b/management/server/config.go @@ -7,6 +7,7 @@ import ( ) type Protocol string +type Provider string const ( UDP Protocol = "udp" @@ -14,6 +15,7 @@ const ( TCP Protocol = "tcp" HTTP Protocol = "http" HTTPS Protocol = "https" + AUTH0 Provider = "auth0" ) // Config of the Management service @@ -27,6 +29,8 @@ type Config struct { HttpConfig *HttpServerConfig IdpManagerConfig *idp.Config + + DeviceAuthorizationFlow *DeviceAuthorizationFlow } // TURNConfig is a config of the TURNCredentialsManager @@ -62,6 +66,26 @@ type Host struct { Password string } +// DeviceAuthorizationFlow represents Device Authorization Flow information +// that can be used by the client to login initiate a Oauth 2.0 device authorization grant flow +// see https://datatracker.ietf.org/doc/html/rfc8628 +type DeviceAuthorizationFlow struct { + Provider string + ProviderConfig ProviderConfig +} + +// ProviderConfig has all attributes needed to initiate a device authorization flow +type ProviderConfig struct { + // ClientID An IDP application client id + ClientID string + // ClientSecret An IDP application client secret + ClientSecret string + // Domain An IDP API domain + Domain string + // Audience An Audience for to authorization validation + Audience string +} + // validateURL validates input http url func validateURL(httpURL string) bool { _, err := url.ParseRequestURI(httpURL) diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index 6144af263..826dd104f 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/netbirdio/netbird/management/server/http/middleware" "github.com/netbirdio/netbird/management/server/jwtclaims" + "strings" "time" "github.com/golang/protobuf/ptypes/timestamp" @@ -47,6 +48,8 @@ func NewServer(config *Config, accountManager AccountManager, peersUpdateManager if err != nil { return nil, status.Errorf(codes.Internal, "unable to create new jwt middleware, err: %v", err) } + } else { + log.Debug("unable to use http config to create new jwt middleware") } return &Server{ @@ -425,3 +428,52 @@ func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.Mana return nil } + +// GetDeviceAuthorizationFlow returns a device authorization flow information +// This is used for initiating an Oauth 2 device authorization grant flow +// which will be used by our clients to Login +func (s *Server) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) { + + peerKey, err := wgtypes.ParseKey(req.GetWgPubKey()) + if err != nil { + errMSG := fmt.Sprintf("error while parsing peer's Wireguard public key %s on GetDeviceAuthorizationFlow request.", req.WgPubKey) + log.Warn(errMSG) + return nil, status.Error(codes.InvalidArgument, errMSG) + } + + err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, &proto.DeviceAuthorizationFlowRequest{}) + if err != nil { + errMSG := fmt.Sprintf("error while decrypting peer's message with Wireguard public key %s.", req.WgPubKey) + log.Warn(errMSG) + return nil, status.Error(codes.InvalidArgument, errMSG) + } + + if s.config.DeviceAuthorizationFlow == nil { + return nil, status.Error(codes.NotFound, "no device authorization flow information available") + } + + provider, ok := proto.DeviceAuthorizationFlowProvider_value[strings.ToUpper(s.config.DeviceAuthorizationFlow.Provider)] + if !ok { + return nil, status.Errorf(codes.InvalidArgument, "no provider found in the protocol for %s", s.config.DeviceAuthorizationFlow.Provider) + } + + flowInfoResp := &proto.DeviceAuthorizationFlow{ + Provider: proto.DeviceAuthorizationFlowProvider(provider), + ProviderConfig: &proto.ProviderConfig{ + ClientID: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientID, + ClientSecret: s.config.DeviceAuthorizationFlow.ProviderConfig.ClientSecret, + Domain: s.config.DeviceAuthorizationFlow.ProviderConfig.Domain, + Audience: s.config.DeviceAuthorizationFlow.ProviderConfig.Audience, + }, + } + + encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, flowInfoResp) + if err != nil { + return nil, status.Error(codes.Internal, "failed to encrypt no device authorization flow information") + } + + return &proto.EncryptedMessage{ + WgPubKey: s.wgKey.PublicKey().String(), + Body: encryptedResp, + }, nil +} diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go index 4427e6abe..03361aad1 100644 --- a/management/server/management_proto_test.go +++ b/management/server/management_proto_test.go @@ -6,7 +6,7 @@ import ( "github.com/netbirdio/netbird/encryption" mgmtProto "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/util" - log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -85,7 +85,7 @@ func Test_SyncProtocol(t *testing.T) { os.Remove(filepath.Join(dir, "store.json")) //nolint }() mport := 33091 - mgmtServer, err := startManagement(mport, &Config{ + mgmtServer, err := startManagement(t, mport, &Config{ Stuns: []*Host{{ Proto: "udp", URI: "stun:stun.wiretrustee.com:3468", @@ -301,7 +301,102 @@ func loginPeerWithValidSetupKey(key wgtypes.Key, client mgmtProto.ManagementServ } -func startManagement(port int, config *Config) (*grpc.Server, error) { +func TestServer_GetDeviceAuthorizationFlow(t *testing.T) { + + testingServerKey, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Errorf("unable to generate server wg key for testing GetDeviceAuthorizationFlow, error: %v", err) + } + + testingClientKey, err := wgtypes.GeneratePrivateKey() + if err != nil { + t.Errorf("unable to generate client wg key for testing GetDeviceAuthorizationFlow, error: %v", err) + } + + testCases := []struct { + name string + inputFlow *DeviceAuthorizationFlow + expectedFlow *mgmtProto.DeviceAuthorizationFlow + expectedErrFunc require.ErrorAssertionFunc + expectedErrMSG string + expectedComparisonFunc require.ComparisonAssertionFunc + expectedComparisonMSG string + }{ + { + name: "Testing No Device Flow Config", + inputFlow: nil, + expectedErrFunc: require.Error, + expectedErrMSG: "should return error", + }, + { + name: "Testing Invalid Device Flow Provider Config", + inputFlow: &DeviceAuthorizationFlow{ + Provider: "NoNe", + ProviderConfig: ProviderConfig{ + ClientID: "test", + }, + }, + expectedErrFunc: require.Error, + expectedErrMSG: "should return error", + }, + { + name: "Testing Full Device Flow Config", + inputFlow: &DeviceAuthorizationFlow{ + Provider: "hosted", + ProviderConfig: ProviderConfig{ + ClientID: "test", + }, + }, + expectedFlow: &mgmtProto.DeviceAuthorizationFlow{ + Provider: 0, + ProviderConfig: &mgmtProto.ProviderConfig{ + ClientID: "test", + }, + }, + expectedErrFunc: require.NoError, + expectedErrMSG: "should not return error", + expectedComparisonFunc: require.Equal, + expectedComparisonMSG: "should match", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + + mgmtServer := &Server{ + wgKey: testingServerKey, + config: &Config{ + DeviceAuthorizationFlow: testCase.inputFlow, + }, + } + + message := &mgmtProto.DeviceAuthorizationFlowRequest{} + + encryptedMSG, err := encryption.EncryptMessage(testingClientKey.PublicKey(), mgmtServer.wgKey, message) + require.NoError(t, err, "should be able to encrypt message") + + resp, err := mgmtServer.GetDeviceAuthorizationFlow( + context.TODO(), + &mgmtProto.EncryptedMessage{ + WgPubKey: testingClientKey.PublicKey().String(), + Body: encryptedMSG, + }, + ) + testCase.expectedErrFunc(t, err, testCase.expectedErrMSG) + if testCase.expectedComparisonFunc != nil { + flowInfoResp := &mgmtProto.DeviceAuthorizationFlow{} + + err = encryption.DecryptMessage(mgmtServer.wgKey.PublicKey(), testingClientKey, resp.Body, flowInfoResp) + require.NoError(t, err, "should be able to decrypt") + + testCase.expectedComparisonFunc(t, testCase.expectedFlow.Provider, flowInfoResp.Provider, testCase.expectedComparisonMSG) + testCase.expectedComparisonFunc(t, testCase.expectedFlow.ProviderConfig.ClientID, flowInfoResp.ProviderConfig.ClientID, testCase.expectedComparisonMSG) + } + }) + } +} + +func startManagement(t *testing.T, port int, config *Config) (*grpc.Server, error) { lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port)) if err != nil { @@ -320,9 +415,10 @@ func startManagement(port int, config *Config) (*grpc.Server, error) { return nil, err } mgmtProto.RegisterManagementServiceServer(s, mgmtServer) + go func() { if err = s.Serve(lis); err != nil { - log.Fatalf("failed to serve: %v", err) + t.Errorf("failed to serve: %v", err) } }() diff --git a/management/server/mock_server/management_server_mock.go b/management/server/mock_server/management_server_mock.go index fee6a4827..97ae5f328 100644 --- a/management/server/mock_server/management_server_mock.go +++ b/management/server/mock_server/management_server_mock.go @@ -11,10 +11,11 @@ import ( type ManagementServiceServerMock struct { proto.UnimplementedManagementServiceServer - LoginFunc func(context.Context, *proto.EncryptedMessage) (*proto.EncryptedMessage, error) - SyncFunc func(*proto.EncryptedMessage, proto.ManagementService_SyncServer) - GetServerKeyFunc func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error) - IsHealthyFunc func(context.Context, *proto.Empty) (*proto.Empty, error) + LoginFunc func(context.Context, *proto.EncryptedMessage) (*proto.EncryptedMessage, error) + SyncFunc func(*proto.EncryptedMessage, proto.ManagementService_SyncServer) + GetServerKeyFunc func(context.Context, *proto.Empty) (*proto.ServerKeyResponse, error) + IsHealthyFunc func(context.Context, *proto.Empty) (*proto.Empty, error) + GetDeviceAuthorizationFlowFunc func(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) } func (m ManagementServiceServerMock) Login(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) { @@ -44,3 +45,10 @@ func (m ManagementServiceServerMock) IsHealthy(ctx context.Context, empty *proto } return nil, status.Errorf(codes.Unimplemented, "method IsHealthy not implemented") } + +func (m ManagementServiceServerMock) GetDeviceAuthorizationFlow(ctx context.Context, req *proto.EncryptedMessage) (*proto.EncryptedMessage, error) { + if m.GetDeviceAuthorizationFlowFunc != nil { + return m.GetDeviceAuthorizationFlowFunc(ctx, req) + } + return nil, status.Errorf(codes.Unimplemented, "method GetDeviceAuthorizationFlow not implemented") +}