From 3194f42ce605fbb24bf2759ed06e5b7611c1939e Mon Sep 17 00:00:00 2001 From: Peter Neuroth Date: Tue, 5 Dec 2023 10:40:39 +0100 Subject: [PATCH] pairing: Attach signer to the pairing process Signer now receives requests from the scheduler for further processing. The signer processes a ApprovePairingRequest and responds to the scheduler. Signed-off-by: Peter Neuroth --- libs/gl-client-py/glclient/scheduler_pb2.py | 26 ++-- libs/gl-client-py/glclient/scheduler_pb2.pyi | 42 +++++++ .../glclient/scheduler_pb2_grpc.py | 76 +++++++++++ libs/gl-client-py/tests/test_pairing.py | 7 ++ libs/gl-client/src/signer/mod.rs | 118 +++++++++++++++++- libs/gl-testing/gltesting/scheduler.py | 30 +++-- libs/gl-testing/gltesting/scheduler_grpc.py | 40 ++++++ libs/proto/scheduler.proto | 29 ++++- 8 files changed, 344 insertions(+), 24 deletions(-) diff --git a/libs/gl-client-py/glclient/scheduler_pb2.py b/libs/gl-client-py/glclient/scheduler_pb2.py index 8c0e52074..848968bae 100644 --- a/libs/gl-client-py/glclient/scheduler_pb2.py +++ b/libs/gl-client-py/glclient/scheduler_pb2.py @@ -14,7 +14,7 @@ from . import greenlight_pb2 as greenlight__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fscheduler.proto\x12\tscheduler\x1a\x10greenlight.proto\"M\n\x10\x43hallengeRequest\x12(\n\x05scope\x18\x01 \x01(\x0e\x32\x19.scheduler.ChallengeScope\x12\x0f\n\x07node_id\x18\x02 \x01(\x0c\"&\n\x11\x43hallengeResponse\x12\x11\n\tchallenge\x18\x01 \x01(\x0c\"\xea\x01\n\x13RegistrationRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\x12\x11\n\tbip32_key\x18\x02 \x01(\x0c\x12\x0f\n\x07network\x18\x04 \x01(\t\x12\x11\n\tchallenge\x18\x05 \x01(\x0c\x12\x11\n\tsignature\x18\x06 \x01(\x0c\x12\x14\n\x0csigner_proto\x18\x07 \x01(\t\x12\x10\n\x08init_msg\x18\x08 \x01(\x0c\x12\x0b\n\x03\x63sr\x18\t \x01(\x0c\x12\x13\n\x0binvite_code\x18\n \x01(\t\x12.\n\x0bstartupmsgs\x18\x03 \x03(\x0b\x32\x19.scheduler.StartupMessage\"[\n\x14RegistrationResponse\x12\x13\n\x0b\x64\x65vice_cert\x18\x01 \x01(\t\x12\x12\n\ndevice_key\x18\x02 \x01(\t\x12\x0c\n\x04rune\x18\x03 \x01(\t\x12\x0c\n\x04\x61uth\x18\x04 \x01(\x0c\"\"\n\x0fScheduleRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\"0\n\x0fNodeInfoRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\x12\x0c\n\x04wait\x18\x02 \x01(\x08\"5\n\x10NodeInfoResponse\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\x12\x10\n\x08grpc_uri\x18\x02 \x01(\t\"U\n\x0fRecoveryRequest\x12\x11\n\tchallenge\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0f\n\x07node_id\x18\x03 \x01(\x0c\x12\x0b\n\x03\x63sr\x18\t \x01(\x0c\"W\n\x10RecoveryResponse\x12\x13\n\x0b\x64\x65vice_cert\x18\x01 \x01(\t\x12\x12\n\ndevice_key\x18\x02 \x01(\t\x12\x0c\n\x04rune\x18\x03 \x01(\t\x12\x0c\n\x04\x61uth\x18\x04 \x01(\x0c\"m\n\x0eUpgradeRequest\x12\x16\n\x0esigner_version\x18\x01 \x01(\t\x12\x13\n\x07initmsg\x18\x02 \x01(\x0c\x42\x02\x18\x01\x12.\n\x0bstartupmsgs\x18\x03 \x03(\x0b\x32\x19.scheduler.StartupMessage\"&\n\x0fUpgradeResponse\x12\x13\n\x0bold_version\x18\x01 \x01(\t\"3\n\x0eStartupMessage\x12\x0f\n\x07request\x18\x01 \x01(\x0c\x12\x10\n\x08response\x18\x02 \x01(\x0c\"\x18\n\x16ListInviteCodesRequest\"J\n\x17ListInviteCodesResponse\x12/\n\x10invite_code_list\x18\x01 \x03(\x0b\x32\x15.scheduler.InviteCode\"/\n\nInviteCode\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x13\n\x0bis_redeemed\x18\x02 \x01(\x08\"\x13\n\x11\x45xportNodeRequest\"!\n\x12\x45xportNodeResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\"\\\n\x0fSignerRejection\x12\x0b\n\x03msg\x18\x01 \x01(\t\x12\'\n\x07request\x18\x02 \x01(\x0b\x32\x16.greenlight.HsmRequest\x12\x13\n\x0bgit_version\x18\x03 \x01(\t\"g\n\x11PairDeviceRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x0b\n\x03\x63sr\x18\x02 \x01(\x0c\x12\x13\n\x0b\x64\x65vice_name\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x65sc\x18\x04 \x01(\t\x12\x0e\n\x06restrs\x18\x05 \x01(\t\"m\n\x12PairDeviceResponse\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65vice_cert\x18\x02 \x01(\t\x12\x12\n\ndevice_key\x18\x03 \x01(\t\x12\x0c\n\x04rune\x18\x04 \x01(\t\x12\x0c\n\x04\x61uth\x18\x05 \x01(\x0c\"\x19\n\tPairingQr\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\t\"+\n\x15GetPairingDataRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\"l\n\x16GetPairingDataResponse\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x0b\n\x03\x63sr\x18\x02 \x01(\x0c\x12\x13\n\x0b\x64\x65vice_name\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x65sc\x18\x04 \x01(\t\x12\x0e\n\x06restrs\x18\x05 \x01(\t\"\x8f\x01\n\x15\x41pprovePairingRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\x04\x12\x0f\n\x07node_id\x18\x03 \x01(\x0c\x12\x13\n\x0b\x64\x65vice_name\x18\x04 \x01(\t\x12\x0e\n\x06restrs\x18\x05 \x01(\t\x12\x0b\n\x03sig\x18\x06 \x01(\x0c\x12\x0c\n\x04rune\x18\x07 \x01(\t\"\x07\n\x05\x45mpty*+\n\x0e\x43hallengeScope\x12\x0c\n\x08REGISTER\x10\x00\x12\x0b\n\x07RECOVER\x10\x01\x32\xf0\x04\n\tScheduler\x12M\n\x08Register\x12\x1e.scheduler.RegistrationRequest\x1a\x1f.scheduler.RegistrationResponse\"\x00\x12\x44\n\x07Recover\x12\x1a.scheduler.RecoveryRequest\x1a\x1b.scheduler.RecoveryResponse\"\x00\x12K\n\x0cGetChallenge\x12\x1b.scheduler.ChallengeRequest\x1a\x1c.scheduler.ChallengeResponse\"\x00\x12\x45\n\x08Schedule\x12\x1a.scheduler.ScheduleRequest\x1a\x1b.scheduler.NodeInfoResponse\"\x00\x12H\n\x0bGetNodeInfo\x12\x1a.scheduler.NodeInfoRequest\x1a\x1b.scheduler.NodeInfoResponse\"\x00\x12G\n\x0cMaybeUpgrade\x12\x19.scheduler.UpgradeRequest\x1a\x1a.scheduler.UpgradeResponse\"\x00\x12Z\n\x0fListInviteCodes\x12!.scheduler.ListInviteCodesRequest\x1a\".scheduler.ListInviteCodesResponse\"\x00\x12K\n\nExportNode\x12\x1c.scheduler.ExportNodeRequest\x1a\x1d.scheduler.ExportNodeResponse\"\x00\x32Q\n\x05\x44\x65\x62ug\x12H\n\x15ReportSignerRejection\x12\x1a.scheduler.SignerRejection\x1a\x11.greenlight.Empty\"\x00\x32\xf7\x01\n\x07Pairing\x12K\n\nPairDevice\x12\x1c.scheduler.PairDeviceRequest\x1a\x1d.scheduler.PairDeviceResponse\"\x00\x12W\n\x0eGetPairingData\x12 .scheduler.GetPairingDataRequest\x1a!.scheduler.GetPairingDataResponse\"\x00\x12\x46\n\x0e\x41pprovePairing\x12 .scheduler.ApprovePairingRequest\x1a\x10.scheduler.Empty\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fscheduler.proto\x12\tscheduler\x1a\x10greenlight.proto\"M\n\x10\x43hallengeRequest\x12(\n\x05scope\x18\x01 \x01(\x0e\x32\x19.scheduler.ChallengeScope\x12\x0f\n\x07node_id\x18\x02 \x01(\x0c\"&\n\x11\x43hallengeResponse\x12\x11\n\tchallenge\x18\x01 \x01(\x0c\"\xea\x01\n\x13RegistrationRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\x12\x11\n\tbip32_key\x18\x02 \x01(\x0c\x12\x0f\n\x07network\x18\x04 \x01(\t\x12\x11\n\tchallenge\x18\x05 \x01(\x0c\x12\x11\n\tsignature\x18\x06 \x01(\x0c\x12\x14\n\x0csigner_proto\x18\x07 \x01(\t\x12\x10\n\x08init_msg\x18\x08 \x01(\x0c\x12\x0b\n\x03\x63sr\x18\t \x01(\x0c\x12\x13\n\x0binvite_code\x18\n \x01(\t\x12.\n\x0bstartupmsgs\x18\x03 \x03(\x0b\x32\x19.scheduler.StartupMessage\"[\n\x14RegistrationResponse\x12\x13\n\x0b\x64\x65vice_cert\x18\x01 \x01(\t\x12\x12\n\ndevice_key\x18\x02 \x01(\t\x12\x0c\n\x04rune\x18\x03 \x01(\t\x12\x0c\n\x04\x61uth\x18\x04 \x01(\x0c\"\"\n\x0fScheduleRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\"0\n\x0fNodeInfoRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\x12\x0c\n\x04wait\x18\x02 \x01(\x08\"5\n\x10NodeInfoResponse\x12\x0f\n\x07node_id\x18\x01 \x01(\x0c\x12\x10\n\x08grpc_uri\x18\x02 \x01(\t\"U\n\x0fRecoveryRequest\x12\x11\n\tchallenge\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0f\n\x07node_id\x18\x03 \x01(\x0c\x12\x0b\n\x03\x63sr\x18\t \x01(\x0c\"W\n\x10RecoveryResponse\x12\x13\n\x0b\x64\x65vice_cert\x18\x01 \x01(\t\x12\x12\n\ndevice_key\x18\x02 \x01(\t\x12\x0c\n\x04rune\x18\x03 \x01(\t\x12\x0c\n\x04\x61uth\x18\x04 \x01(\x0c\"m\n\x0eUpgradeRequest\x12\x16\n\x0esigner_version\x18\x01 \x01(\t\x12\x13\n\x07initmsg\x18\x02 \x01(\x0c\x42\x02\x18\x01\x12.\n\x0bstartupmsgs\x18\x03 \x03(\x0b\x32\x19.scheduler.StartupMessage\"&\n\x0fUpgradeResponse\x12\x13\n\x0bold_version\x18\x01 \x01(\t\"3\n\x0eStartupMessage\x12\x0f\n\x07request\x18\x01 \x01(\x0c\x12\x10\n\x08response\x18\x02 \x01(\x0c\"\x18\n\x16ListInviteCodesRequest\"J\n\x17ListInviteCodesResponse\x12/\n\x10invite_code_list\x18\x01 \x03(\x0b\x32\x15.scheduler.InviteCode\"/\n\nInviteCode\x12\x0c\n\x04\x63ode\x18\x01 \x01(\t\x12\x13\n\x0bis_redeemed\x18\x02 \x01(\x08\"\x13\n\x11\x45xportNodeRequest\"!\n\x12\x45xportNodeResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\"\\\n\x0fSignerRejection\x12\x0b\n\x03msg\x18\x01 \x01(\t\x12\'\n\x07request\x18\x02 \x01(\x0b\x32\x16.greenlight.HsmRequest\x12\x13\n\x0bgit_version\x18\x03 \x01(\t\"g\n\x11PairDeviceRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x0b\n\x03\x63sr\x18\x02 \x01(\x0c\x12\x13\n\x0b\x64\x65vice_name\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x65sc\x18\x04 \x01(\t\x12\x0e\n\x06restrs\x18\x05 \x01(\t\"m\n\x12PairDeviceResponse\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65vice_cert\x18\x02 \x01(\t\x12\x12\n\ndevice_key\x18\x03 \x01(\t\x12\x0c\n\x04rune\x18\x04 \x01(\t\x12\x0c\n\x04\x61uth\x18\x05 \x01(\x0c\"\x19\n\tPairingQr\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\t\"+\n\x15GetPairingDataRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\"l\n\x16GetPairingDataResponse\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x0b\n\x03\x63sr\x18\x02 \x01(\x0c\x12\x13\n\x0b\x64\x65vice_name\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x65sc\x18\x04 \x01(\t\x12\x0e\n\x06restrs\x18\x05 \x01(\t\"\x8f\x01\n\x15\x41pprovePairingRequest\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x11\n\ttimestamp\x18\x02 \x01(\x04\x12\x0f\n\x07node_id\x18\x03 \x01(\x0c\x12\x13\n\x0b\x64\x65vice_name\x18\x04 \x01(\t\x12\x0e\n\x06restrs\x18\x05 \x01(\t\x12\x0b\n\x03sig\x18\x06 \x01(\x0c\x12\x0c\n\x04rune\x18\x07 \x01(\t\"K\n\x16\x41pprovePairingResponse\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\x0c\x12\x0c\n\x04rune\x18\x03 \x01(\t\"\x07\n\x05\x45mpty\"k\n\rSignerRequest\x12\x12\n\nrequest_id\x18\x01 \x01(\r\x12;\n\x0f\x61pprove_pairing\x18\x02 \x01(\x0b\x32 .scheduler.ApprovePairingRequestH\x00\x42\t\n\x07request*+\n\x0e\x43hallengeScope\x12\x0c\n\x08REGISTER\x10\x00\x12\x0b\n\x07RECOVER\x10\x01\x32\xb8\x05\n\tScheduler\x12M\n\x08Register\x12\x1e.scheduler.RegistrationRequest\x1a\x1f.scheduler.RegistrationResponse\"\x00\x12\x44\n\x07Recover\x12\x1a.scheduler.RecoveryRequest\x1a\x1b.scheduler.RecoveryResponse\"\x00\x12K\n\x0cGetChallenge\x12\x1b.scheduler.ChallengeRequest\x1a\x1c.scheduler.ChallengeResponse\"\x00\x12\x45\n\x08Schedule\x12\x1a.scheduler.ScheduleRequest\x1a\x1b.scheduler.NodeInfoResponse\"\x00\x12H\n\x0bGetNodeInfo\x12\x1a.scheduler.NodeInfoRequest\x1a\x1b.scheduler.NodeInfoResponse\"\x00\x12G\n\x0cMaybeUpgrade\x12\x19.scheduler.UpgradeRequest\x1a\x1a.scheduler.UpgradeResponse\"\x00\x12Z\n\x0fListInviteCodes\x12!.scheduler.ListInviteCodesRequest\x1a\".scheduler.ListInviteCodesResponse\"\x00\x12K\n\nExportNode\x12\x1c.scheduler.ExportNodeRequest\x1a\x1d.scheduler.ExportNodeResponse\"\x00\x12\x46\n\x14SignerRequestsStream\x12\x10.scheduler.Empty\x1a\x18.scheduler.SignerRequest\"\x00\x30\x01\x32Q\n\x05\x44\x65\x62ug\x12H\n\x15ReportSignerRejection\x12\x1a.scheduler.SignerRejection\x1a\x11.greenlight.Empty\"\x00\x32\xb9\x02\n\x07Pairing\x12K\n\nPairDevice\x12\x1c.scheduler.PairDeviceRequest\x1a\x1d.scheduler.PairDeviceResponse\"\x00\x12W\n\x0eGetPairingData\x12 .scheduler.GetPairingDataRequest\x1a!.scheduler.GetPairingDataResponse\"\x00\x12\x46\n\x0e\x41pprovePairing\x12 .scheduler.ApprovePairingRequest\x1a\x10.scheduler.Empty\"\x00\x12@\n\x07SignCsr\x12!.scheduler.ApprovePairingResponse\x1a\x10.scheduler.Empty\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -23,8 +23,8 @@ DESCRIPTOR._options = None _UPGRADEREQUEST.fields_by_name['initmsg']._options = None _UPGRADEREQUEST.fields_by_name['initmsg']._serialized_options = b'\030\001' - _globals['_CHALLENGESCOPE']._serialized_start=1872 - _globals['_CHALLENGESCOPE']._serialized_end=1915 + _globals['_CHALLENGESCOPE']._serialized_start=2058 + _globals['_CHALLENGESCOPE']._serialized_end=2101 _globals['_CHALLENGEREQUEST']._serialized_start=48 _globals['_CHALLENGEREQUEST']._serialized_end=125 _globals['_CHALLENGERESPONSE']._serialized_start=127 @@ -73,12 +73,16 @@ _globals['_GETPAIRINGDATARESPONSE']._serialized_end=1715 _globals['_APPROVEPAIRINGREQUEST']._serialized_start=1718 _globals['_APPROVEPAIRINGREQUEST']._serialized_end=1861 - _globals['_EMPTY']._serialized_start=1863 - _globals['_EMPTY']._serialized_end=1870 - _globals['_SCHEDULER']._serialized_start=1918 - _globals['_SCHEDULER']._serialized_end=2542 - _globals['_DEBUG']._serialized_start=2544 - _globals['_DEBUG']._serialized_end=2625 - _globals['_PAIRING']._serialized_start=2628 - _globals['_PAIRING']._serialized_end=2875 + _globals['_APPROVEPAIRINGRESPONSE']._serialized_start=1863 + _globals['_APPROVEPAIRINGRESPONSE']._serialized_end=1938 + _globals['_EMPTY']._serialized_start=1940 + _globals['_EMPTY']._serialized_end=1947 + _globals['_SIGNERREQUEST']._serialized_start=1949 + _globals['_SIGNERREQUEST']._serialized_end=2056 + _globals['_SCHEDULER']._serialized_start=2104 + _globals['_SCHEDULER']._serialized_end=2800 + _globals['_DEBUG']._serialized_start=2802 + _globals['_DEBUG']._serialized_end=2883 + _globals['_PAIRING']._serialized_start=2886 + _globals['_PAIRING']._serialized_end=3199 # @@protoc_insertion_point(module_scope) diff --git a/libs/gl-client-py/glclient/scheduler_pb2.pyi b/libs/gl-client-py/glclient/scheduler_pb2.pyi index 1248142b2..a236cc000 100644 --- a/libs/gl-client-py/glclient/scheduler_pb2.pyi +++ b/libs/gl-client-py/glclient/scheduler_pb2.pyi @@ -688,6 +688,27 @@ class ApprovePairingRequest(google.protobuf.message.Message): global___ApprovePairingRequest = ApprovePairingRequest +@typing_extensions.final +class ApprovePairingResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + SESSION_ID_FIELD_NUMBER: builtins.int + NODE_ID_FIELD_NUMBER: builtins.int + RUNE_FIELD_NUMBER: builtins.int + session_id: builtins.str + node_id: builtins.bytes + rune: builtins.str + def __init__( + self, + *, + session_id: builtins.str = ..., + node_id: builtins.bytes = ..., + rune: builtins.str = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["node_id", b"node_id", "rune", b"rune", "session_id", b"session_id"]) -> None: ... + +global___ApprovePairingResponse = ApprovePairingResponse + @typing_extensions.final class Empty(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor @@ -697,3 +718,24 @@ class Empty(google.protobuf.message.Message): ) -> None: ... global___Empty = Empty + +@typing_extensions.final +class SignerRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + REQUEST_ID_FIELD_NUMBER: builtins.int + APPROVE_PAIRING_FIELD_NUMBER: builtins.int + request_id: builtins.int + @property + def approve_pairing(self) -> global___ApprovePairingRequest: ... + def __init__( + self, + *, + request_id: builtins.int = ..., + approve_pairing: global___ApprovePairingRequest | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["approve_pairing", b"approve_pairing", "request", b"request"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["approve_pairing", b"approve_pairing", "request", b"request", "request_id", b"request_id"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["request", b"request"]) -> typing_extensions.Literal["approve_pairing"] | None: ... + +global___SignerRequest = SignerRequest diff --git a/libs/gl-client-py/glclient/scheduler_pb2_grpc.py b/libs/gl-client-py/glclient/scheduler_pb2_grpc.py index f7083c94a..13a354153 100644 --- a/libs/gl-client-py/glclient/scheduler_pb2_grpc.py +++ b/libs/gl-client-py/glclient/scheduler_pb2_grpc.py @@ -88,6 +88,11 @@ def __init__(self, channel): request_serializer=scheduler__pb2.ExportNodeRequest.SerializeToString, response_deserializer=scheduler__pb2.ExportNodeResponse.FromString, ) + self.SignerRequestsStream = channel.unary_stream( + '/scheduler.Scheduler/SignerRequestsStream', + request_serializer=scheduler__pb2.Empty.SerializeToString, + response_deserializer=scheduler__pb2.SignerRequest.FromString, + ) class SchedulerServicer(object): @@ -270,6 +275,19 @@ def ExportNode(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def SignerRequestsStream(self, request, context): + """Attaches a Signer to a scheduler that streams signer requests + down to the Signer. + + The stream acts in a fire-and-forget way such that it only + streams requests to the signer but does not await a response. + It is used to pass down the AppprovePairingRequest during a + pairing attempt session. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_SchedulerServicer_to_server(servicer, server): rpc_method_handlers = { @@ -313,6 +331,11 @@ def add_SchedulerServicer_to_server(servicer, server): request_deserializer=scheduler__pb2.ExportNodeRequest.FromString, response_serializer=scheduler__pb2.ExportNodeResponse.SerializeToString, ), + 'SignerRequestsStream': grpc.unary_stream_rpc_method_handler( + servicer.SignerRequestsStream, + request_deserializer=scheduler__pb2.Empty.FromString, + response_serializer=scheduler__pb2.SignerRequest.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'scheduler.Scheduler', rpc_method_handlers) @@ -492,6 +515,23 @@ def ExportNode(request, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + @staticmethod + def SignerRequestsStream(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream(request, target, '/scheduler.Scheduler/SignerRequestsStream', + scheduler__pb2.Empty.SerializeToString, + scheduler__pb2.SignerRequest.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + class DebugStub(object): """A service to collect debugging information from clients. @@ -589,6 +629,11 @@ def __init__(self, channel): request_serializer=scheduler__pb2.ApprovePairingRequest.SerializeToString, response_deserializer=scheduler__pb2.Empty.FromString, ) + self.SignCsr = channel.unary_unary( + '/scheduler.Pairing/SignCsr', + request_serializer=scheduler__pb2.ApprovePairingResponse.SerializeToString, + response_deserializer=scheduler__pb2.Empty.FromString, + ) class PairingServicer(object): @@ -622,6 +667,15 @@ def ApprovePairing(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def SignCsr(self, request, context): + """Finishes the pairing process. This is sent by the signer to the + Scheduler to allow to sign the certificate and bundle up the + PairDeviceResponse. + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_PairingServicer_to_server(servicer, server): rpc_method_handlers = { @@ -640,6 +694,11 @@ def add_PairingServicer_to_server(servicer, server): request_deserializer=scheduler__pb2.ApprovePairingRequest.FromString, response_serializer=scheduler__pb2.Empty.SerializeToString, ), + 'SignCsr': grpc.unary_unary_rpc_method_handler( + servicer.SignCsr, + request_deserializer=scheduler__pb2.ApprovePairingResponse.FromString, + response_serializer=scheduler__pb2.Empty.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'scheduler.Pairing', rpc_method_handlers) @@ -701,3 +760,20 @@ def ApprovePairing(request, scheduler__pb2.Empty.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def SignCsr(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/scheduler.Pairing/SignCsr', + scheduler__pb2.ApprovePairingResponse.SerializeToString, + scheduler__pb2.Empty.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/libs/gl-client-py/tests/test_pairing.py b/libs/gl-client-py/tests/test_pairing.py index 9e0e29445..c26831632 100644 --- a/libs/gl-client-py/tests/test_pairing.py +++ b/libs/gl-client-py/tests/test_pairing.py @@ -1,8 +1,12 @@ +import time from gltesting.fixtures import * from glclient import NewDevicePairingClient, AttestationDevicePairingClient from test_scheduler import tls, signer, sclient def test_pairing_session(scheduler, nobody_id, sclient, signer, tls): + # Run the signer in the background + signer.run_in_thread() + name = "new_device" desc = "my description" restrs = "method^list" @@ -46,6 +50,9 @@ def test_pairing_session(scheduler, nobody_id, sclient, signer, tls): # assert(m.rune) fixme: enable once we pass back a rune during the tests. assert(m.auth) + signer.shutdown() + # FIXME: add a blocking shutdown call that waits for the signer to shutdown. + time.sleep(2) def test_paring_data_validation(scheduler, sclient, signer): """A simple test to ensure that data validation works as intended. diff --git a/libs/gl-client/src/signer/mod.rs b/libs/gl-client/src/signer/mod.rs index 44091f1d2..e1339796d 100644 --- a/libs/gl-client/src/signer/mod.rs +++ b/libs/gl-client/src/signer/mod.rs @@ -1,4 +1,10 @@ -use crate::pb::scheduler::{scheduler_client::SchedulerClient, NodeInfoRequest, UpgradeRequest}; +use crate::pb::scheduler::pairing_client::PairingClient; +use crate::pb::scheduler::{ + self, scheduler_client::SchedulerClient, NodeInfoRequest, UpgradeRequest, +}; +use crate::pb::scheduler::{ + signer_request, ApprovePairingRequest, ApprovePairingResponse, SignerRequest, +}; /// The core signer system. It runs in a dedicated thread or using the /// caller thread, streaming incoming requests, verifying them, /// signing if ok, and then shipping the response to the node. @@ -626,7 +632,7 @@ impl Signer { &scheduler_uri ); - let channel = Endpoint::from_shared(scheduler_uri)? + let channel = Endpoint::from_shared(scheduler_uri.clone())? .tls_config(self.tls.inner.clone())? .tcp_keepalive(Some(crate::TCP_KEEPALIVE)) .http2_keep_alive_interval(crate::TCP_KEEPALIVE) @@ -653,9 +659,11 @@ impl Signer { let shutdown = self.shutdown_connector(shutdown, tx.clone()); let node_runner = self.run_forever_node(tx.subscribe(), scheduler.clone()); + let scheduler_runner = + self.run_forever_scheduler(tx.subscribe(), scheduler.clone(), scheduler_uri); - let _ = tokio::join!(node_runner, shutdown); - todo!() + let _ = tokio::join!(node_runner, scheduler_runner, shutdown); + Ok(()) } async fn shutdown_connector( @@ -727,6 +735,108 @@ impl Signer { Ok(()) } + async fn run_forever_scheduler( + &self, + mut shutdown: broadcast::Receiver<()>, + mut scheduler: SchedulerClient, + pairing_uri: String, + ) -> Result<(), anyhow::Error> { + loop { + let rs = scheduler.signer_requests_stream(Request::new(scheduler::Empty::default())); + tokio::select! { + stream = rs => { + let stream = match stream { + Ok(s) => s, + Err(e) => { + trace!("Got an error connecting to the signer_request_stream: {:?}", e); + sleep(Duration::from_millis(1000)).await; + continue; + }, + }; + debug!("Reading from scheduler.signer_request_stream"); + let mut stream = stream.into_inner(); + loop { + let msg = stream.message(); + tokio::select! { + biased; + _ = shutdown.recv() => { + debug!("Receiced the signal to exit the signer loop"); + return Ok(()); + }, + msg = msg => { + let m = match msg { + Ok(m) => {m}, + Err(e) => { + warn!("Got an error receiving from signer_request_stream: {:?}", e); + sleep(Duration::from_millis(1000)).await; + break; + }, + }; + if let Some(msg) = m { + match msg.request { + Some(signer_request::Request::ApprovePairing(req)) => { + debug!("Incoming pairing approval request {:?}", req); + self.process_pairing_approval(req, &pairing_uri).await; + } + None => { + debug!("Received empty SignerRequest form signer_request_stream"); + continue; + } + } + } else { + warn!("signer_request_stream ended, the scheduler shouldn't do this."); + break; + } + }, + } + } + }, + _ = shutdown.recv() => { + debug!("Received the signal to exit the signer loop"); + break; + }, + }; + } + info!("Exiting the signer loop"); + Ok(()) + } + + async fn process_pairing_approval(&self, req: ApprovePairingRequest, pairing_uri: &str) -> () { + debug!("Process pairing request {:?}", req); + + // TODO: DO STUFF + + let endpoint = match Endpoint::from_shared(pairing_uri.to_string()) { + Ok(e) => e, + Err(_) => { + warn!("Could not convert {} to endpoint", pairing_uri); + return; + } + }; + let endpoint = match endpoint.tls_config(self.tls.inner.clone()) { + Ok(e) => e, + Err(_) => { + warn!("Could not set tls config for endpoint {}", pairing_uri); + return; + } + }; + let channel = endpoint + .tcp_keepalive(Some(crate::TCP_KEEPALIVE)) + .http2_keep_alive_interval(crate::TCP_KEEPALIVE) + .keep_alive_timeout(crate::TCP_KEEPALIVE_TIMEOUT) + .keep_alive_while_idle(true) + .connect_lazy(); + + let mut pc = PairingClient::new(channel); + let _ = pc + .sign_csr(ApprovePairingResponse { + session_id: req.session_id, + node_id: req.node_id, + rune: req.rune, + }) + .await; + } + // TODO See comment on `sign_device_key`. pub fn sign_challenge(&self, challenge: Vec) -> Result, anyhow::Error> { if challenge.len() != 32 { diff --git a/libs/gl-testing/gltesting/scheduler.py b/libs/gl-testing/gltesting/scheduler.py index 17d3ccd2d..170e9739e 100644 --- a/libs/gl-testing/gltesting/scheduler.py +++ b/libs/gl-testing/gltesting/scheduler.py @@ -10,10 +10,11 @@ from dataclasses import dataclass from pathlib import Path from threading import Condition -from typing import List, Optional, Dict, Any +from typing import List, Optional, Dict, Any, Generator import purerpc import anyio +from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream from glclient import greenlight_pb2 as greenlightpb from glclient import scheduler_pb2 as schedpb from pyln.client import LightningRpc @@ -98,7 +99,8 @@ def __init__( self.invite_codes: List[str] = [] self.received_invite_code = None self.debugger = DebugServicer() - self.pairings = PairingServicer() + self.pairing_tx_in, self.pairing_rx_in = anyio.create_memory_object_stream() + self.pairings = PairingServicer(stream_out=self.pairing_tx_in) if node_directory is not None: self.node_directory = node_directory @@ -361,6 +363,11 @@ async def ListInviteCodes(self, req) -> schedpb.ListInviteCodesResponse: codes = [schedpb.InviteCode(**c) for c in self.invite_codes] return schedpb.ListInviteCodesResponse(invite_code_list=codes) + async def SignerRequestsStream(self, req) -> Generator[schedpb.SignerRequest, None, None]: + async with self.pairing_rx_in as stream: + data = await stream.receive() + yield schedpb.SignerRequest(request_id=1, approve_pairing=data) + class DebugServicer(schedgrpc.DebugServicer): """Collects and analyzes rejected signer requests.""" @@ -374,11 +381,12 @@ async def ReportSignerRejection(self, report): class PairingServicer(schedgrpc.PairingServicer): """Mocks a pairing backend for local testing""" - def __init__(self): + def __init__(self, stream_out: MemoryObjectSendStream[Any]=None): self.sessions: Dict[int, Dict[str, str | bytes]] = {} - self.send_stream, self.recv_stream = anyio.create_memory_object_stream() + self.stream_out = stream_out + self.internal_send, self.internal_recv = anyio.create_memory_object_stream() - async def recv_once(self, stream: anyio.streams.memory.MemoryObjectReceiveStream[Any]): + async def recv_once(self, stream: MemoryObjectReceiveStream[Any]): async with stream: data = await stream.receive() return data @@ -392,8 +400,8 @@ async def PairDevice(self, req: schedpb.PairDeviceRequest): } self.sessions[req.session_id] = data - # Wait for the Approval of an old device. - await self.recv_once(self.recv_stream) + # Wait for the Approval from the signer. + await self.recv_once(self.internal_recv) device_cert = certs.gencert_from_csr(req.csr, recover=False, pairing=True) return schedpb.PairDeviceResponse( @@ -412,7 +420,13 @@ async def GetPairingData(self, req: schedpb.GetPairingDataRequest): ) async def ApprovePairing(self, req): - async with self.send_stream as send_stream: + async with self.stream_out as stream_out: + await stream_out.send(req) + + return schedpb.Empty() + + async def SignCsr(self, req: schedpb.ApprovePairingResponse): + async with self.internal_send as send_stream: await send_stream.send(req) return schedpb.Empty() diff --git a/libs/gl-testing/gltesting/scheduler_grpc.py b/libs/gl-testing/gltesting/scheduler_grpc.py index 824fcbce3..140bd084d 100644 --- a/libs/gl-testing/gltesting/scheduler_grpc.py +++ b/libs/gl-testing/gltesting/scheduler_grpc.py @@ -27,6 +27,9 @@ async def ListInviteCodes(self, input_message): async def ExportNode(self, input_message): raise NotImplementedError() + + async def SignerRequestsStream(self, input_message): + raise NotImplementedError @property def service(self) -> purerpc.Service: @@ -105,6 +108,15 @@ def service(self) -> purerpc.Service: scheduler__pb2.ExportNodeResponse, ) ) + service_obj.add_method( + "SignerRequestsStream", + self.SignerRequestsStream, + purerpc.RPCSignature( + purerpc.Cardinality.UNARY_STREAM, + scheduler__pb2.ExportNodeRequest, + scheduler__pb2.ExportNodeResponse, + ) + ) return service_obj @@ -178,6 +190,14 @@ def __init__(self, channel): scheduler__pb2.ExportNodeResponse, ) ) + self.SignerRequestsStream = self._client.get_method_stub( + "SignerRequestsStream", + purerpc.RPCSignature( + purerpc.Cardinality.UNARY_STREAM, + scheduler__pb2.Empty, + scheduler__pb2.SignerRequest, + ) + ) class DebugServicer(purerpc.Servicer): @@ -226,6 +246,9 @@ async def GetPairingData(self, input_message): async def ApproveSession(self, input_message): raise NotImplementedError() + + async def SignCsr(self, input_message): + raise NotImplementedError() @property def service(self) -> purerpc.Service: @@ -259,6 +282,15 @@ def service(self) -> purerpc.Service: scheduler__pb2.Empty, ) ) + service_obj.add_method( + "SignCsr", + self.SignCsr, + purerpc.RPCSignature( + purerpc.Cardinality.UNARY_UNARY, + scheduler__pb2.ApprovePairingResponse, + scheduler__pb2.Empty, + ) + ) return service_obj @@ -291,4 +323,12 @@ def __init__(self, channel): scheduler__pb2.ApprovePairingRequest, scheduler__pb2.Empty, ) + ) + self.SignCsr = self._client.get_method_stub( + "SignCsr", + purerpc.RPCSignature( + purerpc.Cardinality.UNARY_UNARY, + scheduler__pb2.ApprovePairingResponse, + scheduler__pb2.Empty, + ) ) \ No newline at end of file diff --git a/libs/proto/scheduler.proto b/libs/proto/scheduler.proto index 2a8c63cca..9c7147b7a 100644 --- a/libs/proto/scheduler.proto +++ b/libs/proto/scheduler.proto @@ -148,6 +148,15 @@ service Scheduler { // being replayed) and loss of funds (see CLN Backups // documentation for more information) rpc ExportNode(ExportNodeRequest) returns (ExportNodeResponse) {} + + // Attaches a Signer to a scheduler that streams signer requests + // down to the Signer. + // + // The stream acts in a fire-and-forget way such that it only + // streams requests to the signer but does not await a response. + // It is used to pass down the AppprovePairingRequest during a + // pairing attempt session. + rpc SignerRequestsStream(Empty) returns (stream SignerRequest) {}; }; // A service to collect debugging information from clients. @@ -178,6 +187,11 @@ service Pairing { // Approves a pairing request. The ApprovePairingRequest is // forwarded to a signer for further processing. rpc ApprovePairing(ApprovePairingRequest) returns (Empty) {} + + // Finishes the pairing process. This is sent by the signer to the + // Scheduler to allow to sign the certificate and bundle up the + // PairDeviceResponse. + rpc SignCsr(ApprovePairingResponse) returns (Empty) {} } message ChallengeRequest { @@ -477,4 +491,17 @@ message ApprovePairingRequest { string rune = 7; } -message Empty {} \ No newline at end of file +message ApprovePairingResponse { + string session_id = 1; + bytes node_id = 2; + string rune =3; +} + +message Empty {} + +message SignerRequest { + uint32 request_id = 1; + oneof request { + ApprovePairingRequest approve_pairing = 2; + } +} \ No newline at end of file