From 8e346445edfc05e6dd5aefe2974523feae1f7255 Mon Sep 17 00:00:00 2001 From: joerger Date: Tue, 31 Oct 2023 16:35:39 -0700 Subject: [PATCH 1/7] Add PromptMFA to tshd event service. --- .../lib/teleterm/v1/tshd_events_service.pb.go | 244 +++++++++-- .../v1/tshd_events_service_grpc.pb.go | 39 ++ .../v1/tshd_events_service_grpc_pb.d.ts | 17 + .../v1/tshd_events_service_grpc_pb.js | 34 ++ .../teleterm/v1/tshd_events_service_pb.d.ts | 54 +++ .../lib/teleterm/v1/tshd_events_service_pb.js | 394 ++++++++++++++++++ .../lib/teleterm/v1/tshd_events_service.proto | 15 + .../teleterm/src/services/tshdEvents/index.ts | 5 + 8 files changed, 762 insertions(+), 40 deletions(-) diff --git a/gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service.pb.go b/gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service.pb.go index 0e5c40adc297e..2db5c44a407ef 100644 --- a/gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service.pb.go +++ b/gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service.pb.go @@ -481,6 +481,124 @@ func (*SendPendingHeadlessAuthenticationResponse) Descriptor() ([]byte, []int) { return file_teleport_lib_teleterm_v1_tshd_events_service_proto_rawDescGZIP(), []int{7} } +type PromptMFARequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RootClusterUri string `protobuf:"bytes,1,opt,name=root_cluster_uri,json=rootClusterUri,proto3" json:"root_cluster_uri,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + Totp bool `protobuf:"varint,3,opt,name=totp,proto3" json:"totp,omitempty"` + Webauthn bool `protobuf:"varint,4,opt,name=webauthn,proto3" json:"webauthn,omitempty"` +} + +func (x *PromptMFARequest) Reset() { + *x = PromptMFARequest{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PromptMFARequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PromptMFARequest) ProtoMessage() {} + +func (x *PromptMFARequest) ProtoReflect() protoreflect.Message { + mi := &file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes[8] + 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 PromptMFARequest.ProtoReflect.Descriptor instead. +func (*PromptMFARequest) Descriptor() ([]byte, []int) { + return file_teleport_lib_teleterm_v1_tshd_events_service_proto_rawDescGZIP(), []int{8} +} + +func (x *PromptMFARequest) GetRootClusterUri() string { + if x != nil { + return x.RootClusterUri + } + return "" +} + +func (x *PromptMFARequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *PromptMFARequest) GetTotp() bool { + if x != nil { + return x.Totp + } + return false +} + +func (x *PromptMFARequest) GetWebauthn() bool { + if x != nil { + return x.Webauthn + } + return false +} + +type PromptMFAResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TotpCode string `protobuf:"bytes,1,opt,name=totp_code,json=totpCode,proto3" json:"totp_code,omitempty"` +} + +func (x *PromptMFAResponse) Reset() { + *x = PromptMFAResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PromptMFAResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PromptMFAResponse) ProtoMessage() {} + +func (x *PromptMFAResponse) ProtoReflect() protoreflect.Message { + mi := &file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes[9] + 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 PromptMFAResponse.ProtoReflect.Descriptor instead. +func (*PromptMFAResponse) Descriptor() ([]byte, []int) { + return file_teleport_lib_teleterm_v1_tshd_events_service_proto_rawDescGZIP(), []int{9} +} + +func (x *PromptMFAResponse) GetTotpCode() string { + if x != nil { + return x.TotpCode + } + return "" +} + var File_teleport_lib_teleterm_v1_tshd_events_service_proto protoreflect.FileDescriptor var file_teleport_lib_teleterm_v1_tshd_events_service_proto_rawDesc = []byte{ @@ -542,39 +660,57 @@ var file_teleport_lib_teleterm_v1_tshd_events_service_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x22, 0x2b, 0x0a, 0x29, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x9d, 0x03, 0x0a, 0x11, 0x54, 0x73, 0x68, 0x64, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5e, 0x0a, - 0x07, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, - 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, - 0x10, 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, - 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, - 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xac, 0x01, 0x0a, 0x21, 0x53, 0x65, 0x6e, - 0x64, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, - 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x42, - 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, - 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, - 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x54, 0x5a, 0x52, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, 0x65, 0x6e, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2f, 0x6c, 0x69, 0x62, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2f, - 0x76, 0x31, 0x3b, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x76, 0x31, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x84, 0x01, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x6d, + 0x70, 0x74, 0x4d, 0x46, 0x41, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x28, 0x0a, 0x10, + 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x69, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x55, 0x72, 0x69, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x74, 0x6f, 0x74, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x74, 0x6f, + 0x74, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x65, 0x62, 0x61, 0x75, 0x74, 0x68, 0x6e, 0x22, 0x30, + 0x0a, 0x11, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x4d, 0x46, 0x41, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x6f, 0x74, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x6f, 0x74, 0x70, 0x43, 0x6f, 0x64, 0x65, + 0x32, 0x83, 0x04, 0x0a, 0x11, 0x54, 0x73, 0x68, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x5e, 0x0a, 0x07, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, + 0x6e, 0x12, 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, + 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0xac, 0x01, 0x0a, 0x21, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x42, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, + 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x50, 0x65, 0x6e, 0x64, 0x69, + 0x6e, 0x67, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x64, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x4d, 0x46, 0x41, 0x12, 0x2a, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x4d, + 0x46, 0x41, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6c, 0x69, 0x62, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, + 0x6d, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x4d, 0x46, 0x41, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x54, 0x5a, 0x52, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2f, 0x6c, 0x69, 0x62, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2f, 0x76, + 0x31, 0x3b, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -589,7 +725,7 @@ func file_teleport_lib_teleterm_v1_tshd_events_service_proto_rawDescGZIP() []byt return file_teleport_lib_teleterm_v1_tshd_events_service_proto_rawDescData } -var file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_teleport_lib_teleterm_v1_tshd_events_service_proto_goTypes = []interface{}{ (*ReloginRequest)(nil), // 0: teleport.lib.teleterm.v1.ReloginRequest (*GatewayCertExpired)(nil), // 1: teleport.lib.teleterm.v1.GatewayCertExpired @@ -599,6 +735,8 @@ var file_teleport_lib_teleterm_v1_tshd_events_service_proto_goTypes = []interfac (*SendNotificationResponse)(nil), // 5: teleport.lib.teleterm.v1.SendNotificationResponse (*SendPendingHeadlessAuthenticationRequest)(nil), // 6: teleport.lib.teleterm.v1.SendPendingHeadlessAuthenticationRequest (*SendPendingHeadlessAuthenticationResponse)(nil), // 7: teleport.lib.teleterm.v1.SendPendingHeadlessAuthenticationResponse + (*PromptMFARequest)(nil), // 8: teleport.lib.teleterm.v1.PromptMFARequest + (*PromptMFAResponse)(nil), // 9: teleport.lib.teleterm.v1.PromptMFAResponse } var file_teleport_lib_teleterm_v1_tshd_events_service_proto_depIdxs = []int32{ 1, // 0: teleport.lib.teleterm.v1.ReloginRequest.gateway_cert_expired:type_name -> teleport.lib.teleterm.v1.GatewayCertExpired @@ -606,11 +744,13 @@ var file_teleport_lib_teleterm_v1_tshd_events_service_proto_depIdxs = []int32{ 0, // 2: teleport.lib.teleterm.v1.TshdEventsService.Relogin:input_type -> teleport.lib.teleterm.v1.ReloginRequest 3, // 3: teleport.lib.teleterm.v1.TshdEventsService.SendNotification:input_type -> teleport.lib.teleterm.v1.SendNotificationRequest 6, // 4: teleport.lib.teleterm.v1.TshdEventsService.SendPendingHeadlessAuthentication:input_type -> teleport.lib.teleterm.v1.SendPendingHeadlessAuthenticationRequest - 2, // 5: teleport.lib.teleterm.v1.TshdEventsService.Relogin:output_type -> teleport.lib.teleterm.v1.ReloginResponse - 5, // 6: teleport.lib.teleterm.v1.TshdEventsService.SendNotification:output_type -> teleport.lib.teleterm.v1.SendNotificationResponse - 7, // 7: teleport.lib.teleterm.v1.TshdEventsService.SendPendingHeadlessAuthentication:output_type -> teleport.lib.teleterm.v1.SendPendingHeadlessAuthenticationResponse - 5, // [5:8] is the sub-list for method output_type - 2, // [2:5] is the sub-list for method input_type + 8, // 5: teleport.lib.teleterm.v1.TshdEventsService.PromptMFA:input_type -> teleport.lib.teleterm.v1.PromptMFARequest + 2, // 6: teleport.lib.teleterm.v1.TshdEventsService.Relogin:output_type -> teleport.lib.teleterm.v1.ReloginResponse + 5, // 7: teleport.lib.teleterm.v1.TshdEventsService.SendNotification:output_type -> teleport.lib.teleterm.v1.SendNotificationResponse + 7, // 8: teleport.lib.teleterm.v1.TshdEventsService.SendPendingHeadlessAuthentication:output_type -> teleport.lib.teleterm.v1.SendPendingHeadlessAuthenticationResponse + 9, // 9: teleport.lib.teleterm.v1.TshdEventsService.PromptMFA:output_type -> teleport.lib.teleterm.v1.PromptMFAResponse + 6, // [6:10] is the sub-list for method output_type + 2, // [2:6] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name @@ -718,6 +858,30 @@ func file_teleport_lib_teleterm_v1_tshd_events_service_proto_init() { return nil } } + file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PromptMFARequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PromptMFAResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_teleport_lib_teleterm_v1_tshd_events_service_proto_msgTypes[0].OneofWrappers = []interface{}{ (*ReloginRequest_GatewayCertExpired)(nil), @@ -731,7 +895,7 @@ func file_teleport_lib_teleterm_v1_tshd_events_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_lib_teleterm_v1_tshd_events_service_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service_grpc.pb.go b/gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service_grpc.pb.go index f5869bf383495..31a5eb8ac45bb 100644 --- a/gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service_grpc.pb.go +++ b/gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service_grpc.pb.go @@ -36,6 +36,7 @@ const ( TshdEventsService_Relogin_FullMethodName = "/teleport.lib.teleterm.v1.TshdEventsService/Relogin" TshdEventsService_SendNotification_FullMethodName = "/teleport.lib.teleterm.v1.TshdEventsService/SendNotification" TshdEventsService_SendPendingHeadlessAuthentication_FullMethodName = "/teleport.lib.teleterm.v1.TshdEventsService/SendPendingHeadlessAuthentication" + TshdEventsService_PromptMFA_FullMethodName = "/teleport.lib.teleterm.v1.TshdEventsService/PromptMFA" ) // TshdEventsServiceClient is the client API for TshdEventsService service. @@ -52,6 +53,8 @@ type TshdEventsServiceClient interface { // SendPendingHeadlessAuthentication notifies the Electron app of a pending headless authentication, // which it can use to initiate headless authentication resolution in the UI. SendPendingHeadlessAuthentication(ctx context.Context, in *SendPendingHeadlessAuthenticationRequest, opts ...grpc.CallOption) (*SendPendingHeadlessAuthenticationResponse, error) + // PromptMFA notifies the Electron app that the daemon is waiting for the user to answer an MFA prompt. + PromptMFA(ctx context.Context, in *PromptMFARequest, opts ...grpc.CallOption) (*PromptMFAResponse, error) } type tshdEventsServiceClient struct { @@ -89,6 +92,15 @@ func (c *tshdEventsServiceClient) SendPendingHeadlessAuthentication(ctx context. return out, nil } +func (c *tshdEventsServiceClient) PromptMFA(ctx context.Context, in *PromptMFARequest, opts ...grpc.CallOption) (*PromptMFAResponse, error) { + out := new(PromptMFAResponse) + err := c.cc.Invoke(ctx, TshdEventsService_PromptMFA_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // TshdEventsServiceServer is the server API for TshdEventsService service. // All implementations must embed UnimplementedTshdEventsServiceServer // for forward compatibility @@ -103,6 +115,8 @@ type TshdEventsServiceServer interface { // SendPendingHeadlessAuthentication notifies the Electron app of a pending headless authentication, // which it can use to initiate headless authentication resolution in the UI. SendPendingHeadlessAuthentication(context.Context, *SendPendingHeadlessAuthenticationRequest) (*SendPendingHeadlessAuthenticationResponse, error) + // PromptMFA notifies the Electron app that the daemon is waiting for the user to answer an MFA prompt. + PromptMFA(context.Context, *PromptMFARequest) (*PromptMFAResponse, error) mustEmbedUnimplementedTshdEventsServiceServer() } @@ -119,6 +133,9 @@ func (UnimplementedTshdEventsServiceServer) SendNotification(context.Context, *S func (UnimplementedTshdEventsServiceServer) SendPendingHeadlessAuthentication(context.Context, *SendPendingHeadlessAuthenticationRequest) (*SendPendingHeadlessAuthenticationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SendPendingHeadlessAuthentication not implemented") } +func (UnimplementedTshdEventsServiceServer) PromptMFA(context.Context, *PromptMFARequest) (*PromptMFAResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PromptMFA not implemented") +} func (UnimplementedTshdEventsServiceServer) mustEmbedUnimplementedTshdEventsServiceServer() {} // UnsafeTshdEventsServiceServer may be embedded to opt out of forward compatibility for this service. @@ -186,6 +203,24 @@ func _TshdEventsService_SendPendingHeadlessAuthentication_Handler(srv interface{ return interceptor(ctx, in, info, handler) } +func _TshdEventsService_PromptMFA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PromptMFARequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TshdEventsServiceServer).PromptMFA(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TshdEventsService_PromptMFA_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TshdEventsServiceServer).PromptMFA(ctx, req.(*PromptMFARequest)) + } + return interceptor(ctx, in, info, handler) +} + // TshdEventsService_ServiceDesc is the grpc.ServiceDesc for TshdEventsService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -205,6 +240,10 @@ var TshdEventsService_ServiceDesc = grpc.ServiceDesc{ MethodName: "SendPendingHeadlessAuthentication", Handler: _TshdEventsService_SendPendingHeadlessAuthentication_Handler, }, + { + MethodName: "PromptMFA", + Handler: _TshdEventsService_PromptMFA_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "teleport/lib/teleterm/v1/tshd_events_service.proto", diff --git a/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_grpc_pb.d.ts b/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_grpc_pb.d.ts index bb9fe4b9e37b9..62e8591d99385 100644 --- a/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_grpc_pb.d.ts +++ b/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_grpc_pb.d.ts @@ -11,6 +11,7 @@ interface ITshdEventsServiceService extends grpc.ServiceDefinition { @@ -40,6 +41,15 @@ interface ITshdEventsServiceService_ISendPendingHeadlessAuthentication extends g responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } +interface ITshdEventsServiceService_IPromptMFA extends grpc.MethodDefinition { + path: "/teleport.lib.teleterm.v1.TshdEventsService/PromptMFA"; + requestStream: false; + responseStream: false; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} export const TshdEventsServiceService: ITshdEventsServiceService; @@ -47,6 +57,7 @@ export interface ITshdEventsServiceServer { relogin: grpc.handleUnaryCall; sendNotification: grpc.handleUnaryCall; sendPendingHeadlessAuthentication: grpc.handleUnaryCall; + promptMFA: grpc.handleUnaryCall; } export interface ITshdEventsServiceClient { @@ -59,6 +70,9 @@ export interface ITshdEventsServiceClient { sendPendingHeadlessAuthentication(request: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationRequest, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse) => void): grpc.ClientUnaryCall; sendPendingHeadlessAuthentication(request: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse) => void): grpc.ClientUnaryCall; sendPendingHeadlessAuthentication(request: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse) => void): grpc.ClientUnaryCall; + promptMFA(request: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse) => void): grpc.ClientUnaryCall; + promptMFA(request: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse) => void): grpc.ClientUnaryCall; + promptMFA(request: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse) => void): grpc.ClientUnaryCall; } export class TshdEventsServiceClient extends grpc.Client implements ITshdEventsServiceClient { @@ -72,4 +86,7 @@ export class TshdEventsServiceClient extends grpc.Client implements ITshdEventsS public sendPendingHeadlessAuthentication(request: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationRequest, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse) => void): grpc.ClientUnaryCall; public sendPendingHeadlessAuthentication(request: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse) => void): grpc.ClientUnaryCall; public sendPendingHeadlessAuthentication(request: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse) => void): grpc.ClientUnaryCall; + public promptMFA(request: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse) => void): grpc.ClientUnaryCall; + public promptMFA(request: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse) => void): grpc.ClientUnaryCall; + public promptMFA(request: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse) => void): grpc.ClientUnaryCall; } diff --git a/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_grpc_pb.js b/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_grpc_pb.js index 0490ab3d36b8f..1e0018350571b 100644 --- a/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_grpc_pb.js +++ b/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_grpc_pb.js @@ -19,6 +19,28 @@ var grpc = require('@grpc/grpc-js'); var teleport_lib_teleterm_v1_tshd_events_service_pb = require('../../../../teleport/lib/teleterm/v1/tshd_events_service_pb.js'); +function serialize_teleport_lib_teleterm_v1_PromptMFARequest(arg) { + if (!(arg instanceof teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest)) { + throw new Error('Expected argument of type teleport.lib.teleterm.v1.PromptMFARequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_teleport_lib_teleterm_v1_PromptMFARequest(buffer_arg) { + return teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_teleport_lib_teleterm_v1_PromptMFAResponse(arg) { + if (!(arg instanceof teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse)) { + throw new Error('Expected argument of type teleport.lib.teleterm.v1.PromptMFAResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_teleport_lib_teleterm_v1_PromptMFAResponse(buffer_arg) { + return teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_teleport_lib_teleterm_v1_ReloginRequest(arg) { if (!(arg instanceof teleport_lib_teleterm_v1_tshd_events_service_pb.ReloginRequest)) { throw new Error('Expected argument of type teleport.lib.teleterm.v1.ReloginRequest'); @@ -129,6 +151,18 @@ sendPendingHeadlessAuthentication: { responseSerialize: serialize_teleport_lib_teleterm_v1_SendPendingHeadlessAuthenticationResponse, responseDeserialize: deserialize_teleport_lib_teleterm_v1_SendPendingHeadlessAuthenticationResponse, }, + // PromptMFA notifies the Electron app that the daemon is waiting for the user to answer an MFA prompt. +promptMFA: { + path: '/teleport.lib.teleterm.v1.TshdEventsService/PromptMFA', + requestStream: false, + responseStream: false, + requestType: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, + responseType: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse, + requestSerialize: serialize_teleport_lib_teleterm_v1_PromptMFARequest, + requestDeserialize: deserialize_teleport_lib_teleterm_v1_PromptMFARequest, + responseSerialize: serialize_teleport_lib_teleterm_v1_PromptMFAResponse, + responseDeserialize: deserialize_teleport_lib_teleterm_v1_PromptMFAResponse, + }, }; exports.TshdEventsServiceClient = grpc.makeGenericClientConstructor(TshdEventsServiceService); diff --git a/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_pb.d.ts b/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_pb.d.ts index 7161dc44acbab..85afcc3f393b8 100644 --- a/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_pb.d.ts +++ b/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_pb.d.ts @@ -211,3 +211,57 @@ export namespace SendPendingHeadlessAuthenticationResponse { export type AsObject = { } } + +export class PromptMFARequest extends jspb.Message { + getRootClusterUri(): string; + setRootClusterUri(value: string): PromptMFARequest; + + getReason(): string; + setReason(value: string): PromptMFARequest; + + getTotp(): boolean; + setTotp(value: boolean): PromptMFARequest; + + getWebauthn(): boolean; + setWebauthn(value: boolean): PromptMFARequest; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): PromptMFARequest.AsObject; + static toObject(includeInstance: boolean, msg: PromptMFARequest): PromptMFARequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: PromptMFARequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): PromptMFARequest; + static deserializeBinaryFromReader(message: PromptMFARequest, reader: jspb.BinaryReader): PromptMFARequest; +} + +export namespace PromptMFARequest { + export type AsObject = { + rootClusterUri: string, + reason: string, + totp: boolean, + webauthn: boolean, + } +} + +export class PromptMFAResponse extends jspb.Message { + getTotpCode(): string; + setTotpCode(value: string): PromptMFAResponse; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): PromptMFAResponse.AsObject; + static toObject(includeInstance: boolean, msg: PromptMFAResponse): PromptMFAResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: PromptMFAResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): PromptMFAResponse; + static deserializeBinaryFromReader(message: PromptMFAResponse, reader: jspb.BinaryReader): PromptMFAResponse; +} + +export namespace PromptMFAResponse { + export type AsObject = { + totpCode: string, + } +} diff --git a/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_pb.js b/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_pb.js index ae6a674565dc0..16e4448af5472 100644 --- a/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_pb.js +++ b/gen/proto/js/teleport/lib/teleterm/v1/tshd_events_service_pb.js @@ -17,6 +17,8 @@ var global = (function() { return this || window || global || self || Function(' goog.exportSymbol('proto.teleport.lib.teleterm.v1.CannotProxyGatewayConnection', null, global); goog.exportSymbol('proto.teleport.lib.teleterm.v1.GatewayCertExpired', null, global); +goog.exportSymbol('proto.teleport.lib.teleterm.v1.PromptMFARequest', null, global); +goog.exportSymbol('proto.teleport.lib.teleterm.v1.PromptMFAResponse', null, global); goog.exportSymbol('proto.teleport.lib.teleterm.v1.ReloginRequest', null, global); goog.exportSymbol('proto.teleport.lib.teleterm.v1.ReloginRequest.ReasonCase', null, global); goog.exportSymbol('proto.teleport.lib.teleterm.v1.ReloginResponse', null, global); @@ -193,6 +195,48 @@ if (goog.DEBUG && !COMPILED) { */ proto.teleport.lib.teleterm.v1.SendPendingHeadlessAuthenticationResponse.displayName = 'proto.teleport.lib.teleterm.v1.SendPendingHeadlessAuthenticationResponse'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.teleport.lib.teleterm.v1.PromptMFARequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.teleport.lib.teleterm.v1.PromptMFARequest.displayName = 'proto.teleport.lib.teleterm.v1.PromptMFARequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.teleport.lib.teleterm.v1.PromptMFAResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.teleport.lib.teleterm.v1.PromptMFAResponse.displayName = 'proto.teleport.lib.teleterm.v1.PromptMFAResponse'; +} /** * Oneof group definitions for this message. Each group defines the field @@ -1418,4 +1462,354 @@ proto.teleport.lib.teleterm.v1.SendPendingHeadlessAuthenticationResponse.seriali }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.toObject = function(opt_includeInstance) { + return proto.teleport.lib.teleterm.v1.PromptMFARequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.teleport.lib.teleterm.v1.PromptMFARequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.toObject = function(includeInstance, msg) { + var f, obj = { + rootClusterUri: jspb.Message.getFieldWithDefault(msg, 1, ""), + reason: jspb.Message.getFieldWithDefault(msg, 2, ""), + totp: jspb.Message.getBooleanFieldWithDefault(msg, 3, false), + webauthn: jspb.Message.getBooleanFieldWithDefault(msg, 4, false) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.teleport.lib.teleterm.v1.PromptMFARequest} + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.teleport.lib.teleterm.v1.PromptMFARequest; + return proto.teleport.lib.teleterm.v1.PromptMFARequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.teleport.lib.teleterm.v1.PromptMFARequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.teleport.lib.teleterm.v1.PromptMFARequest} + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setRootClusterUri(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setReason(value); + break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setTotp(value); + break; + case 4: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setWebauthn(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.teleport.lib.teleterm.v1.PromptMFARequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.teleport.lib.teleterm.v1.PromptMFARequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getRootClusterUri(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getReason(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getTotp(); + if (f) { + writer.writeBool( + 3, + f + ); + } + f = message.getWebauthn(); + if (f) { + writer.writeBool( + 4, + f + ); + } +}; + + +/** + * optional string root_cluster_uri = 1; + * @return {string} + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.getRootClusterUri = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.lib.teleterm.v1.PromptMFARequest} returns this + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.setRootClusterUri = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string reason = 2; + * @return {string} + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.getReason = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.lib.teleterm.v1.PromptMFARequest} returns this + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.setReason = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional bool totp = 3; + * @return {boolean} + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.getTotp = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.teleport.lib.teleterm.v1.PromptMFARequest} returns this + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.setTotp = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + +/** + * optional bool webauthn = 4; + * @return {boolean} + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.getWebauthn = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.teleport.lib.teleterm.v1.PromptMFARequest} returns this + */ +proto.teleport.lib.teleterm.v1.PromptMFARequest.prototype.setWebauthn = function(value) { + return jspb.Message.setProto3BooleanField(this, 4, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse.prototype.toObject = function(opt_includeInstance) { + return proto.teleport.lib.teleterm.v1.PromptMFAResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.teleport.lib.teleterm.v1.PromptMFAResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse.toObject = function(includeInstance, msg) { + var f, obj = { + totpCode: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.teleport.lib.teleterm.v1.PromptMFAResponse} + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.teleport.lib.teleterm.v1.PromptMFAResponse; + return proto.teleport.lib.teleterm.v1.PromptMFAResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.teleport.lib.teleterm.v1.PromptMFAResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.teleport.lib.teleterm.v1.PromptMFAResponse} + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setTotpCode(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.teleport.lib.teleterm.v1.PromptMFAResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.teleport.lib.teleterm.v1.PromptMFAResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getTotpCode(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string totp_code = 1; + * @return {string} + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse.prototype.getTotpCode = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.lib.teleterm.v1.PromptMFAResponse} returns this + */ +proto.teleport.lib.teleterm.v1.PromptMFAResponse.prototype.setTotpCode = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + goog.object.extend(exports, proto.teleport.lib.teleterm.v1); diff --git a/proto/teleport/lib/teleterm/v1/tshd_events_service.proto b/proto/teleport/lib/teleterm/v1/tshd_events_service.proto index 013aaf3788deb..74fa2017bb49f 100644 --- a/proto/teleport/lib/teleterm/v1/tshd_events_service.proto +++ b/proto/teleport/lib/teleterm/v1/tshd_events_service.proto @@ -31,6 +31,8 @@ service TshdEventsService { // SendPendingHeadlessAuthentication notifies the Electron app of a pending headless authentication, // which it can use to initiate headless authentication resolution in the UI. rpc SendPendingHeadlessAuthentication(SendPendingHeadlessAuthenticationRequest) returns (SendPendingHeadlessAuthenticationResponse); + // PromptMFA notifies the Electron app that the daemon is waiting for the user to answer an MFA prompt. + rpc PromptMFA(PromptMFARequest) returns (PromptMFAResponse); } // Relogin @@ -84,3 +86,16 @@ message SendPendingHeadlessAuthenticationRequest { } message SendPendingHeadlessAuthenticationResponse {} + +// PromptMFA + +message PromptMFARequest { + string root_cluster_uri = 1; + string reason = 2; + bool totp = 3; + bool webauthn = 4; +} + +message PromptMFAResponse { + string totp_code = 1; +} diff --git a/web/packages/teleterm/src/services/tshdEvents/index.ts b/web/packages/teleterm/src/services/tshdEvents/index.ts index 8c98badf3e032..133f972a77c94 100644 --- a/web/packages/teleterm/src/services/tshdEvents/index.ts +++ b/web/packages/teleterm/src/services/tshdEvents/index.ts @@ -205,7 +205,12 @@ function createService(logger: Logger): { } ); }, + promptMFA: (call, callback) => { + // TODO (joerger): Handle MFA prompt with totp/webauthn modal. + logger.info('Received prompt mfa request'); + }, }; + return { service, subscribeToTshdEvent }; } From 188fc3aaa92de067fd6e4d61a233f28eeb98d3f6 Mon Sep 17 00:00:00 2001 From: joerger Date: Tue, 31 Oct 2023 16:35:57 -0700 Subject: [PATCH 2/7] Add custom MFA prompt using the tshd event service PromptMFA method. --- lib/client/api.go | 4 + lib/client/mfa.go | 5 + lib/client/mfa/cli.go | 10 +- lib/client/mfa/prompt.go | 20 ++-- lib/teleterm/daemon/daemon.go | 7 +- lib/teleterm/daemon/mfaPrompt.go | 156 +++++++++++++++++++++++++++++++ 6 files changed, 186 insertions(+), 16 deletions(-) create mode 100644 lib/teleterm/daemon/mfaPrompt.go diff --git a/lib/client/api.go b/lib/client/api.go index 9ec26213fb9c9..2dd76098fe09e 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -70,6 +70,7 @@ import ( "github.com/gravitational/teleport/lib/auth/touchid" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" "github.com/gravitational/teleport/lib/authz" + libmfa "github.com/gravitational/teleport/lib/client/mfa" "github.com/gravitational/teleport/lib/client/terminal" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/devicetrust" @@ -472,6 +473,9 @@ type Config struct { // SSHLogDir is the directory to log the output of multiple SSH commands to. // If not set, no logs will be created. SSHLogDir string + + // MFAPrompt is a custom MFA prompt constructor to use when prompting for MFA. + MFAPromptConstructor func(cfg *libmfa.PromptConfig) mfa.Prompt } // CachePolicy defines cache policy for local clients diff --git a/lib/client/mfa.go b/lib/client/mfa.go index f4734b90a06bc..958a48c3af64e 100644 --- a/lib/client/mfa.go +++ b/lib/client/mfa.go @@ -32,7 +32,12 @@ type WebauthnLoginFunc func(ctx context.Context, origin string, assertion *wanty // NewMFAPrompt creates a new MFA prompt from client settings. func (tc *TeleportClient) NewMFAPrompt(opts ...mfa.PromptOpt) mfa.Prompt { cfg := tc.newPromptConfig(opts...) + var prompt mfa.Prompt = libmfa.NewCLIPrompt(cfg, tc.Stderr) + if tc.MFAPromptConstructor != nil { + prompt = tc.MFAPromptConstructor(cfg) + } + return prompt } diff --git a/lib/client/mfa/cli.go b/lib/client/mfa/cli.go index 25b87b0f38444..7a1297456e193 100644 --- a/lib/client/mfa/cli.go +++ b/lib/client/mfa/cli.go @@ -53,7 +53,7 @@ func (c *CLIPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChalleng } var wg sync.WaitGroup - runOpts, err := c.cfg.getRunOptions(ctx, chal) + runOpts, err := c.cfg.GetRunOptions(ctx, chal) if err != nil { return nil, trace.Wrap(err) } @@ -82,7 +82,7 @@ func (c *CLIPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChalleng } // Fire TOTP goroutine. - if runOpts.promptTOTP { + if runOpts.PromptTOTP { wg.Add(1) go func() { defer wg.Done() @@ -97,14 +97,14 @@ func (c *CLIPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChalleng } // Fire Webauthn goroutine. - if runOpts.promptWebauthn { + if runOpts.PromptWebauthn { wg.Add(1) go func() { defer wg.Done() // get webauthn prompt and wrap with otp context handler. prompt := &webauthnPromptWithOTP{ - LoginPrompt: c.getWebauthnPrompt(ctx, runOpts.promptTOTP), + LoginPrompt: c.getWebauthnPrompt(ctx, runOpts.PromptTOTP), otpCancelAndWait: otpCancelAndWait, } @@ -185,7 +185,7 @@ func (c *CLIPrompt) getWebauthnPrompt(ctx context.Context, withTOTP bool) wancli func (c *CLIPrompt) promptWebauthn(ctx context.Context, chal *proto.MFAAuthenticateChallenge, prompt wancli.LoginPrompt) (*proto.MFAAuthenticateResponse, error) { opts := &wancli.LoginOpts{AuthenticatorAttachment: c.cfg.AuthenticatorAttachment} - resp, _, err := c.cfg.WebauthnLoginFunc(ctx, c.cfg.getWebauthnOrigin(), wantypes.CredentialAssertionFromProto(chal.WebauthnChallenge), prompt, opts) + resp, _, err := c.cfg.WebauthnLoginFunc(ctx, c.cfg.GetWebauthnOrigin(), wantypes.CredentialAssertionFromProto(chal.WebauthnChallenge), prompt, opts) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/client/mfa/prompt.go b/lib/client/mfa/prompt.go index 0a67eaf5dc4de..fa58c2b4c175f 100644 --- a/lib/client/mfa/prompt.go +++ b/lib/client/mfa/prompt.go @@ -72,25 +72,25 @@ func NewPromptConfig(proxyAddr string, opts ...mfa.PromptOpt) *PromptConfig { return cfg } -// runOpts are mfa prompt run options. -type runOpts struct { - promptTOTP bool - promptWebauthn bool +// RunOpts are mfa prompt run options. +type RunOpts struct { + PromptTOTP bool + PromptWebauthn bool } -// getRunOptions gets mfa prompt run options by cross referencing the mfa challenge with prompt configuration. -func (c PromptConfig) getRunOptions(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (runOpts, error) { +// GetRunOptions gets mfa prompt run options by cross referencing the mfa challenge with prompt configuration. +func (c PromptConfig) GetRunOptions(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (RunOpts, error) { promptTOTP := chal.TOTP != nil promptWebauthn := chal.WebauthnChallenge != nil if !promptTOTP && !promptWebauthn { - return runOpts{}, trace.BadParameter("mfa challenge is empty") + return RunOpts{}, trace.BadParameter("mfa challenge is empty") } // Does the current platform support hardware MFA? Adjust accordingly. switch { case !promptTOTP && !c.WebauthnSupported: - return runOpts{}, trace.BadParameter("hardware device MFA not supported by your platform, please register an OTP device") + return RunOpts{}, trace.BadParameter("hardware device MFA not supported by your platform, please register an OTP device") case !c.WebauthnSupported: // Do not prompt for hardware devices, it won't work. promptWebauthn = false @@ -108,10 +108,10 @@ func (c PromptConfig) getRunOptions(ctx context.Context, chal *proto.MFAAuthenti promptTOTP = false } - return runOpts{promptTOTP, promptWebauthn}, nil + return RunOpts{promptTOTP, promptWebauthn}, nil } -func (c PromptConfig) getWebauthnOrigin() string { +func (c PromptConfig) GetWebauthnOrigin() string { if !strings.HasPrefix(c.ProxyAddress, "https://") { return "https://" + c.ProxyAddress } diff --git a/lib/teleterm/daemon/daemon.go b/lib/teleterm/daemon/daemon.go index 80d976e542359..ec851deabd955 100644 --- a/lib/teleterm/daemon/daemon.go +++ b/lib/teleterm/daemon/daemon.go @@ -216,7 +216,12 @@ func (s *Service) ResolveCluster(path string) (*clusters.Cluster, *client.Telepo // worry about parsing URIs and can assume they are correct. func (s *Service) ResolveClusterURI(uri uri.ResourceURI) (*clusters.Cluster, *client.TeleportClient, error) { cluster, clusterClient, err := s.cfg.Storage.GetByResourceURI(uri) - return cluster, clusterClient, trace.Wrap(err) + if err != nil { + return nil, nil, trace.Wrap(err) + } + + clusterClient.MFAPromptConstructor = s.NewMFAPromptConstructor(cluster.URI.String()) + return cluster, clusterClient, nil } // ResolveClusterWithDetails returns fully detailed cluster information. It makes requests to the auth server and includes diff --git a/lib/teleterm/daemon/mfaPrompt.go b/lib/teleterm/daemon/mfaPrompt.go new file mode 100644 index 0000000000000..dd716c0e4fac5 --- /dev/null +++ b/lib/teleterm/daemon/mfaPrompt.go @@ -0,0 +1,156 @@ +// Copyright 2023 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package daemon + +import ( + "context" + "errors" + "io" + "sync" + + "github.com/gravitational/trace" + "github.com/gravitational/trace/trail" + "github.com/sirupsen/logrus" + + "github.com/gravitational/teleport/api/client/proto" + "github.com/gravitational/teleport/api/mfa" + api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1" + wancli "github.com/gravitational/teleport/lib/auth/webauthncli" + wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" + libmfa "github.com/gravitational/teleport/lib/client/mfa" +) + +// mfaPrompt is a tshd implementation of mfa.Prompt that uses the +// tshdEventsClient to propagate mfa prompts to the Electron App. +type mfaPrompt struct { + cfg libmfa.PromptConfig + clusterURI string + tshdEventsClient api.TshdEventsServiceClient + logger *logrus.Logger +} + +// NewMFAPromptConstructor returns a new MFA prompt constructor +// for this service and the given cluster. +func (s *Service) NewMFAPromptConstructor(clusterURI string) func(cfg *libmfa.PromptConfig) mfa.Prompt { + return func(cfg *libmfa.PromptConfig) mfa.Prompt { + return s.NewMFAPrompt(clusterURI, cfg) + } +} + +// NewMFAPrompt returns a new MFA prompt for this service and the given cluster. +func (s *Service) NewMFAPrompt(clusterURI string, cfg *libmfa.PromptConfig) *mfaPrompt { + return &mfaPrompt{ + cfg: *cfg, + clusterURI: clusterURI, + tshdEventsClient: s.tshdEventsClient, + logger: s.cfg.Log.Logger, + } +} + +// Run prompts the user to complete an MFA authentication challenge. +func (p *mfaPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { + runOpts, err := p.cfg.GetRunOptions(ctx, chal) + if err != nil { + return nil, trace.Wrap(err) + } + + type response struct { + kind string + resp *proto.MFAAuthenticateResponse + err error + } + respC := make(chan response, 2) + var wg sync.WaitGroup + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Fire Electron notification goroutine. + wg.Add(1) + go func() { + defer wg.Done() + + resp, err := p.promptApp(ctx, chal, runOpts) + respC <- response{kind: "APP", resp: resp, err: err} + }() + + // Fire Webauthn goroutine. + if runOpts.PromptWebauthn { + wg.Add(1) + go func() { + defer wg.Done() + + resp, err := p.promptWebauthn(ctx, chal) + respC <- response{kind: "WEBAUTHN", resp: resp, err: err} + }() + } + + // Wait for the 1-2 authn goroutines above to complete, then close respC. + go func() { + wg.Wait() + close(respC) + }() + + // Wait for a successful response, or terminating error, from the 1-2 authn goroutines. + // The goroutine above will ensure the response channel is closed once all goroutines are done. + for resp := range respC { + switch err := resp.err; { + case errors.Is(err, wancli.ErrUsingNonRegisteredDevice): + // Surface error immediately. + return nil, trace.Wrap(resp.err) + case err != nil: + p.logger.WithError(err).Debugf("%s authentication failed", resp.kind) + // Continue to give the other authn goroutine a chance to succeed. + // If both have failed, this will exit the loop. + continue + } + + // Return successful response. + return resp.resp, nil + } + + // If no successful response is returned, this means the authn goroutines were unsuccessful. + // This usually occurs when the prompt times out or no devices are available to prompt. + // Return a user readable error message. + return nil, trace.BadParameter("failed to authenticate using available MFA devices, rerun the command with '-d' to see error details for each device") +} + +func (p *mfaPrompt) promptWebauthn(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { + prompt := wancli.NewDefaultPrompt(ctx, io.Discard) + opts := &wancli.LoginOpts{AuthenticatorAttachment: p.cfg.AuthenticatorAttachment} + resp, _, err := p.cfg.WebauthnLoginFunc(ctx, p.cfg.GetWebauthnOrigin(), wantypes.CredentialAssertionFromProto(chal.WebauthnChallenge), prompt, opts) + if err != nil { + return nil, trace.Wrap(err) + } + + return resp, nil +} + +func (p *mfaPrompt) promptApp(ctx context.Context, chal *proto.MFAAuthenticateChallenge, runOpts libmfa.RunOpts) (*proto.MFAAuthenticateResponse, error) { + resp, err := p.tshdEventsClient.PromptMFA(ctx, &api.PromptMFARequest{ + RootClusterUri: p.clusterURI, + Reason: p.cfg.PromptReason, + Totp: runOpts.PromptTOTP, + Webauthn: runOpts.PromptWebauthn, + }) + if err != nil { + return nil, trail.FromGRPC(err) + } + return &proto.MFAAuthenticateResponse{ + Response: &proto.MFAAuthenticateResponse_TOTP{ + TOTP: &proto.TOTPResponse{Code: resp.TotpCode}, + }, + }, nil +} From 9e0b1741fb463a56b3d56f59382cac31bca8bfc1 Mon Sep 17 00:00:00 2001 From: joerger Date: Fri, 3 Nov 2023 14:02:02 -0700 Subject: [PATCH 3/7] Generalize and reuse mfa prompt goroutine logic. --- lib/client/mfa/cli.go | 116 ++++++++++--------------------- lib/client/mfa/prompt.go | 58 ++++++++++++++-- lib/teleterm/daemon/mfaPrompt.go | 69 ++++-------------- 3 files changed, 104 insertions(+), 139 deletions(-) diff --git a/lib/client/mfa/cli.go b/lib/client/mfa/cli.go index 7a1297456e193..e775c4390a845 100644 --- a/lib/client/mfa/cli.go +++ b/lib/client/mfa/cli.go @@ -18,7 +18,6 @@ package mfa import ( "context" - "errors" "fmt" "io" "sync" @@ -52,95 +51,56 @@ func (c *CLIPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChalleng fmt.Fprintln(c.writer, c.cfg.PromptReason) } - var wg sync.WaitGroup runOpts, err := c.cfg.GetRunOptions(ctx, chal) if err != nil { return nil, trace.Wrap(err) } - type response struct { - kind string - resp *proto.MFAAuthenticateResponse - err error - } - respC := make(chan response, 2) - - ctx, cancel := context.WithCancel(ctx) - defer func() { - cancel() - // Wait for all goroutines to complete to ensure there are no leaks. - wg.Wait() - }() - - // Use variables below to cancel OTP reads and make sure the goroutine exited. - otpCtx, otpCancel := context.WithCancel(ctx) - defer otpCancel() - otpDone := make(chan struct{}) - otpCancelAndWait := func() { - otpCancel() - <-otpDone - } - - // Fire TOTP goroutine. - if runOpts.PromptTOTP { - wg.Add(1) - go func() { - defer wg.Done() - defer close(otpDone) - - // Let Webauthn take the prompt below if applicable. - quiet := c.cfg.Quiet || runOpts.promptWebauthn + // Depending on the run opts, we may spawn a TOTP goroutine, webauth goroutine, or both. + spawnGoroutines := func(ctx context.Context, wg *sync.WaitGroup, respC chan<- MFAGoroutineResponse) { + // Use variables below to cancel OTP reads and make sure the goroutine exited. + otpCtx, otpCancel := context.WithCancel(ctx) + defer otpCancel() + otpDone := make(chan struct{}) + otpCancelAndWait := func() { + otpCancel() + <-otpDone + } - resp, err := c.promptTOTP(otpCtx, chal, quiet) - respC <- response{kind: "TOTP", resp: resp, err: err} - }() - } + // Fire TOTP goroutine. + if runOpts.PromptTOTP { + wg.Add(1) + go func() { + defer wg.Done() + defer close(otpDone) - // Fire Webauthn goroutine. - if runOpts.PromptWebauthn { - wg.Add(1) - go func() { - defer wg.Done() - - // get webauthn prompt and wrap with otp context handler. - prompt := &webauthnPromptWithOTP{ - LoginPrompt: c.getWebauthnPrompt(ctx, runOpts.PromptTOTP), - otpCancelAndWait: otpCancelAndWait, - } - - resp, err := c.promptWebauthn(ctx, chal, prompt) - respC <- response{kind: "WEBAUTHN", resp: resp, err: err} - }() - } + // Let Webauthn take the prompt below if applicable. + quiet := c.cfg.Quiet || runOpts.PromptWebauthn - // Wait for the 1-2 authn goroutines above to complete, then close respC. - go func() { - wg.Wait() - close(respC) - }() - - // Wait for a successful response, or terminating error, from the 1-2 authn goroutines. - // The goroutine above will ensure the response channel is closed once all goroutines are done. - for resp := range respC { - switch err := resp.err; { - case errors.Is(err, wancli.ErrUsingNonRegisteredDevice): - // Surface error immediately. - return nil, trace.Wrap(resp.err) - case err != nil: - c.cfg.Log.WithError(err).Debugf("%s authentication failed", resp.kind) - // Continue to give the other authn goroutine a chance to succeed. - // If both have failed, this will exit the loop. - continue + resp, err := c.promptTOTP(otpCtx, chal, quiet) + respC <- MFAGoroutineResponse{Resp: resp, Err: trace.Wrap(err, "TOTP authentication failed")} + }() } - // Return successful response. - return resp.resp, nil + // Fire Webauthn goroutine. + if runOpts.PromptWebauthn { + wg.Add(1) + go func() { + defer wg.Done() + + // Get webauthn prompt and wrap with otp context handler. + prompt := &webauthnPromptWithOTP{ + LoginPrompt: c.getWebauthnPrompt(ctx, runOpts.PromptTOTP), + otpCancelAndWait: otpCancelAndWait, + } + + resp, err := c.promptWebauthn(ctx, chal, prompt) + respC <- MFAGoroutineResponse{Resp: resp, Err: trace.Wrap(err, "Webauthn authentication failed")} + }() + } } - // If no successful response is returned, this means the authn goroutines were unsuccessful. - // This usually occurs when the prompt times out or no devices are available to prompt. - // Return a user readable error message. - return nil, trace.BadParameter("failed to authenticate using available MFA devices, rerun the command with '-d' to see error details for each device") + return HandleMFAPromptGoroutines(ctx, spawnGoroutines) } func (c *CLIPrompt) promptTOTP(ctx context.Context, chal *proto.MFAAuthenticateChallenge, quiet bool) (*proto.MFAAuthenticateResponse, error) { diff --git a/lib/client/mfa/prompt.go b/lib/client/mfa/prompt.go index fa58c2b4c175f..43fff60f09867 100644 --- a/lib/client/mfa/prompt.go +++ b/lib/client/mfa/prompt.go @@ -18,12 +18,12 @@ package mfa import ( "context" + "errors" "strings" + "sync" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/mfa" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" @@ -50,8 +50,6 @@ type PromptConfig struct { PreferOTP bool // WebauthnSupported indicates whether Webauthn is supported. WebauthnSupported bool - // Log is a logging entry. - Log *logrus.Entry } // NewPromptConfig returns a prompt config that will induce default behavior. @@ -60,9 +58,6 @@ func NewPromptConfig(proxyAddr string, opts ...mfa.PromptOpt) *PromptConfig { ProxyAddress: proxyAddr, WebauthnLoginFunc: wancli.Login, WebauthnSupported: wancli.HasPlatformSupport(), - Log: logrus.WithFields(logrus.Fields{ - trace.Component: teleport.ComponentClient, - }), } for _, opt := range opts { @@ -117,3 +112,52 @@ func (c PromptConfig) GetWebauthnOrigin() string { } return c.ProxyAddress } + +// MFAGoroutineResponse is an MFA goroutine response. +type MFAGoroutineResponse struct { + Resp *proto.MFAAuthenticateResponse + Err error +} + +// HandleMFAPromptGoroutines spawns MFA prompt goroutines and returns the first successful response, +// terminating error, or an aggregated error if they all fail. +func HandleMFAPromptGoroutines(ctx context.Context, startGoroutines func(context.Context, *sync.WaitGroup, chan<- MFAGoroutineResponse)) (*proto.MFAAuthenticateResponse, error) { + respC := make(chan MFAGoroutineResponse, 2) + var wg sync.WaitGroup + + ctx, cancel := context.WithCancel(ctx) + defer func() { + cancel() + // wait for all goroutines to complete to ensure there are no leaks. + wg.Wait() + }() + + startGoroutines(ctx, &wg, respC) + + // Wait for spawned goroutines above to complete, then close respC. + go func() { + wg.Wait() + close(respC) + }() + + // Wait for a successful response, or terminating error, from the spawned goroutines. + // The goroutine above will ensure the response channel is closed once all goroutines are done. + var errs []error + for resp := range respC { + switch err := resp.Err; { + case errors.Is(err, wancli.ErrUsingNonRegisteredDevice): + // Surface error immediately. + return nil, trace.Wrap(resp.Err) + case err != nil: + errs = append(errs, err) + // Continue to give the other authn goroutine a chance to succeed. + // If both have failed, this will exit the loop. + continue + } + + // Return successful response. + return resp.Resp, nil + } + + return nil, trace.Wrap(trace.NewAggregate(errs...), "failed to authenticate using available MFA devices") +} diff --git a/lib/teleterm/daemon/mfaPrompt.go b/lib/teleterm/daemon/mfaPrompt.go index dd716c0e4fac5..7a13ea87dc175 100644 --- a/lib/teleterm/daemon/mfaPrompt.go +++ b/lib/teleterm/daemon/mfaPrompt.go @@ -16,13 +16,11 @@ package daemon import ( "context" - "errors" "io" "sync" "github.com/gravitational/trace" "github.com/gravitational/trace/trail" - "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/mfa" @@ -38,7 +36,6 @@ type mfaPrompt struct { cfg libmfa.PromptConfig clusterURI string tshdEventsClient api.TshdEventsServiceClient - logger *logrus.Logger } // NewMFAPromptConstructor returns a new MFA prompt constructor @@ -55,7 +52,6 @@ func (s *Service) NewMFAPrompt(clusterURI string, cfg *libmfa.PromptConfig) *mfa cfg: *cfg, clusterURI: clusterURI, tshdEventsClient: s.tshdEventsClient, - logger: s.cfg.Log.Logger, } } @@ -66,65 +62,30 @@ func (p *mfaPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChalleng return nil, trace.Wrap(err) } - type response struct { - kind string - resp *proto.MFAAuthenticateResponse - err error - } - respC := make(chan response, 2) - var wg sync.WaitGroup - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // Fire Electron notification goroutine. - wg.Add(1) - go func() { - defer wg.Done() - - resp, err := p.promptApp(ctx, chal, runOpts) - respC <- response{kind: "APP", resp: resp, err: err} - }() - - // Fire Webauthn goroutine. - if runOpts.PromptWebauthn { + // Depending on the run opts, we may spawn a TOTP goroutine, webauth goroutine, or both. + spawnGoroutines := func(ctx context.Context, wg *sync.WaitGroup, respC chan<- libmfa.MFAGoroutineResponse) { + // Fire App goroutine (TOTP). wg.Add(1) go func() { defer wg.Done() - resp, err := p.promptWebauthn(ctx, chal) - respC <- response{kind: "WEBAUTHN", resp: resp, err: err} + resp, err := p.promptApp(ctx, chal, runOpts) + respC <- libmfa.MFAGoroutineResponse{Resp: resp, Err: err} }() - } - // Wait for the 1-2 authn goroutines above to complete, then close respC. - go func() { - wg.Wait() - close(respC) - }() - - // Wait for a successful response, or terminating error, from the 1-2 authn goroutines. - // The goroutine above will ensure the response channel is closed once all goroutines are done. - for resp := range respC { - switch err := resp.err; { - case errors.Is(err, wancli.ErrUsingNonRegisteredDevice): - // Surface error immediately. - return nil, trace.Wrap(resp.err) - case err != nil: - p.logger.WithError(err).Debugf("%s authentication failed", resp.kind) - // Continue to give the other authn goroutine a chance to succeed. - // If both have failed, this will exit the loop. - continue - } + // Fire Webauthn goroutine. + if runOpts.PromptWebauthn { + wg.Add(1) + go func() { + defer wg.Done() - // Return successful response. - return resp.resp, nil + resp, err := p.promptWebauthn(ctx, chal) + respC <- libmfa.MFAGoroutineResponse{Resp: resp, Err: trace.Wrap(err, "Webauthn authentication failed")} + }() + } } - // If no successful response is returned, this means the authn goroutines were unsuccessful. - // This usually occurs when the prompt times out or no devices are available to prompt. - // Return a user readable error message. - return nil, trace.BadParameter("failed to authenticate using available MFA devices, rerun the command with '-d' to see error details for each device") + return libmfa.HandleMFAPromptGoroutines(ctx, spawnGoroutines) } func (p *mfaPrompt) promptWebauthn(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { From c2ade2169a9e7c4d45e6f116191d61b9228e5889 Mon Sep 17 00:00:00 2001 From: joerger Date: Fri, 3 Nov 2023 14:16:53 -0700 Subject: [PATCH 4/7] Use important modal sempahore for MFA prompts. --- lib/teleterm/daemon/mfaPrompt.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/lib/teleterm/daemon/mfaPrompt.go b/lib/teleterm/daemon/mfaPrompt.go index 7a13ea87dc175..89e1269a68de1 100644 --- a/lib/teleterm/daemon/mfaPrompt.go +++ b/lib/teleterm/daemon/mfaPrompt.go @@ -33,9 +33,9 @@ import ( // mfaPrompt is a tshd implementation of mfa.Prompt that uses the // tshdEventsClient to propagate mfa prompts to the Electron App. type mfaPrompt struct { - cfg libmfa.PromptConfig - clusterURI string - tshdEventsClient api.TshdEventsServiceClient + cfg libmfa.PromptConfig + clusterURI string + promptAppMFA func(ctx context.Context, in *api.PromptMFARequest) (*api.PromptMFAResponse, error) } // NewMFAPromptConstructor returns a new MFA prompt constructor @@ -49,12 +49,21 @@ func (s *Service) NewMFAPromptConstructor(clusterURI string) func(cfg *libmfa.Pr // NewMFAPrompt returns a new MFA prompt for this service and the given cluster. func (s *Service) NewMFAPrompt(clusterURI string, cfg *libmfa.PromptConfig) *mfaPrompt { return &mfaPrompt{ - cfg: *cfg, - clusterURI: clusterURI, - tshdEventsClient: s.tshdEventsClient, + cfg: *cfg, + clusterURI: clusterURI, + promptAppMFA: s.promptAppMFA, } } +func (s *Service) promptAppMFA(ctx context.Context, in *api.PromptMFARequest) (*api.PromptMFAResponse, error) { + if err := s.importantModalSemaphore.Acquire(ctx); err != nil { + return nil, trace.Wrap(err) + } + defer s.importantModalSemaphore.Release() + + return s.tshdEventsClient.PromptMFA(ctx, in) +} + // Run prompts the user to complete an MFA authentication challenge. func (p *mfaPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) { runOpts, err := p.cfg.GetRunOptions(ctx, chal) @@ -69,7 +78,7 @@ func (p *mfaPrompt) Run(ctx context.Context, chal *proto.MFAAuthenticateChalleng go func() { defer wg.Done() - resp, err := p.promptApp(ctx, chal, runOpts) + resp, err := p.promptMFA(ctx, chal, runOpts) respC <- libmfa.MFAGoroutineResponse{Resp: resp, Err: err} }() @@ -99,8 +108,8 @@ func (p *mfaPrompt) promptWebauthn(ctx context.Context, chal *proto.MFAAuthentic return resp, nil } -func (p *mfaPrompt) promptApp(ctx context.Context, chal *proto.MFAAuthenticateChallenge, runOpts libmfa.RunOpts) (*proto.MFAAuthenticateResponse, error) { - resp, err := p.tshdEventsClient.PromptMFA(ctx, &api.PromptMFARequest{ +func (p *mfaPrompt) promptMFA(ctx context.Context, chal *proto.MFAAuthenticateChallenge, runOpts libmfa.RunOpts) (*proto.MFAAuthenticateResponse, error) { + resp, err := p.promptAppMFA(ctx, &api.PromptMFARequest{ RootClusterUri: p.clusterURI, Reason: p.cfg.PromptReason, Totp: runOpts.PromptTOTP, From b89cc019379a12ba830f7c3f687c72b572e973a7 Mon Sep 17 00:00:00 2001 From: Brian Joerger Date: Tue, 21 Nov 2023 10:15:16 -0800 Subject: [PATCH 5/7] Update lib/client/api.go Co-authored-by: Lisa Kim --- lib/client/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client/api.go b/lib/client/api.go index 2dd76098fe09e..a50417b665360 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -474,7 +474,7 @@ type Config struct { // If not set, no logs will be created. SSHLogDir string - // MFAPrompt is a custom MFA prompt constructor to use when prompting for MFA. + // MFAPromptConstructor is a custom MFA prompt constructor to use when prompting for MFA. MFAPromptConstructor func(cfg *libmfa.PromptConfig) mfa.Prompt } From a65f99d28fce4d10fc32fa60b99ae26e03b4ab20 Mon Sep 17 00:00:00 2001 From: joerger Date: Mon, 27 Nov 2023 19:40:01 -0800 Subject: [PATCH 6/7] Fix file name. --- lib/teleterm/daemon/{mfaPrompt.go => mfaprompt.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/teleterm/daemon/{mfaPrompt.go => mfaprompt.go} (100%) diff --git a/lib/teleterm/daemon/mfaPrompt.go b/lib/teleterm/daemon/mfaprompt.go similarity index 100% rename from lib/teleterm/daemon/mfaPrompt.go rename to lib/teleterm/daemon/mfaprompt.go From 363472d79de3086a3f122ad4605029a3d91a1cd7 Mon Sep 17 00:00:00 2001 From: joerger Date: Tue, 28 Nov 2023 12:04:30 -0800 Subject: [PATCH 7/7] Fix lint. --- web/packages/teleterm/src/services/tshdEvents/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/packages/teleterm/src/services/tshdEvents/index.ts b/web/packages/teleterm/src/services/tshdEvents/index.ts index 133f972a77c94..a5f0c6606238c 100644 --- a/web/packages/teleterm/src/services/tshdEvents/index.ts +++ b/web/packages/teleterm/src/services/tshdEvents/index.ts @@ -205,12 +205,11 @@ function createService(logger: Logger): { } ); }, - promptMFA: (call, callback) => { + promptMFA: () => { // TODO (joerger): Handle MFA prompt with totp/webauthn modal. logger.info('Received prompt mfa request'); }, }; - return { service, subscribeToTshdEvent }; }