Teleport Connect MFA prompt (#34135)

* Add PromptMFA to tshd event service.

* Add custom MFA prompt using the tshd event service PromptMFA method.

* Generalize and reuse mfa prompt goroutine logic.

* Use important modal sempahore for MFA prompts.

* Update lib/client/api.go

Co-authored-by: Lisa Kim <[email protected]>

* Fix file name.

* Fix lint.

---------

Co-authored-by: Lisa Kim <[email protected]>
This commit is contained in:
Brian Joerger
2023-12-01 22:43:24 +00:00
committed by GitHub
co-authored by Lisa Kim
parent c8a81a4b0f
commit 704f3fccd3
14 changed files with 1006 additions and 141 deletions
@@ -484,6 +484,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{
@@ -545,39 +663,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 (
@@ -592,7 +728,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
@@ -602,6 +738,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
@@ -609,11 +747,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
@@ -721,6 +861,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),
@@ -734,7 +898,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,
},
@@ -39,6 +39,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.
@@ -55,6 +56,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 {
@@ -92,6 +95,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
@@ -106,6 +118,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()
}
@@ -122,6 +136,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.
@@ -189,6 +206,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)
@@ -208,6 +243,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",
+17
View File
@@ -11,6 +11,7 @@ interface ITshdEventsServiceService extends grpc.ServiceDefinition<grpc.UntypedS
relogin: ITshdEventsServiceService_IRelogin;
sendNotification: ITshdEventsServiceService_ISendNotification;
sendPendingHeadlessAuthentication: ITshdEventsServiceService_ISendPendingHeadlessAuthentication;
promptMFA: ITshdEventsServiceService_IPromptMFA;
}
interface ITshdEventsServiceService_IRelogin extends grpc.MethodDefinition<teleport_lib_teleterm_v1_tshd_events_service_pb.ReloginRequest, teleport_lib_teleterm_v1_tshd_events_service_pb.ReloginResponse> {
@@ -40,6 +41,15 @@ interface ITshdEventsServiceService_ISendPendingHeadlessAuthentication extends g
responseSerialize: grpc.serialize<teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse>;
responseDeserialize: grpc.deserialize<teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse>;
}
interface ITshdEventsServiceService_IPromptMFA extends grpc.MethodDefinition<teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse> {
path: "/teleport.lib.teleterm.v1.TshdEventsService/PromptMFA";
requestStream: false;
responseStream: false;
requestSerialize: grpc.serialize<teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest>;
requestDeserialize: grpc.deserialize<teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest>;
responseSerialize: grpc.serialize<teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse>;
responseDeserialize: grpc.deserialize<teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse>;
}
export const TshdEventsServiceService: ITshdEventsServiceService;
@@ -47,6 +57,7 @@ export interface ITshdEventsServiceServer {
relogin: grpc.handleUnaryCall<teleport_lib_teleterm_v1_tshd_events_service_pb.ReloginRequest, teleport_lib_teleterm_v1_tshd_events_service_pb.ReloginResponse>;
sendNotification: grpc.handleUnaryCall<teleport_lib_teleterm_v1_tshd_events_service_pb.SendNotificationRequest, teleport_lib_teleterm_v1_tshd_events_service_pb.SendNotificationResponse>;
sendPendingHeadlessAuthentication: grpc.handleUnaryCall<teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationRequest, teleport_lib_teleterm_v1_tshd_events_service_pb.SendPendingHeadlessAuthenticationResponse>;
promptMFA: grpc.handleUnaryCall<teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFARequest, teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse>;
}
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<grpc.CallOptions>, 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<grpc.CallOptions>, 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<grpc.CallOptions>, 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<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: teleport_lib_teleterm_v1_tshd_events_service_pb.PromptMFAResponse) => void): grpc.ClientUnaryCall;
}
@@ -22,6 +22,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');
@@ -132,6 +154,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);
+54
View File
@@ -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<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
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<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
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,
}
}
@@ -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_<name>, 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_<name>, 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);
+4
View File
@@ -72,6 +72,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"
@@ -474,6 +475,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
// MFAPromptConstructor 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
+5
View File
@@ -34,7 +34,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
}
+43 -83
View File
@@ -20,7 +20,6 @@ package mfa
import (
"context"
"errors"
"fmt"
"io"
"sync"
@@ -54,95 +53,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)
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
resp, err := c.promptTOTP(otpCtx, chal, quiet)
respC <- response{kind: "TOTP", resp: resp, err: err}
}()
}
// 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}
}()
}
// 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
// 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
}
// Return successful response.
return resp.resp, nil
// 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
resp, err := c.promptTOTP(otpCtx, chal, quiet)
respC <- MFAGoroutineResponse{Resp: resp, Err: trace.Wrap(err, "TOTP authentication failed")}
}()
}
// 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) {
@@ -187,7 +147,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)
}
+61 -17
View File
@@ -20,12 +20,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"
@@ -52,8 +52,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.
@@ -62,9 +60,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 {
@@ -74,25 +69,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
@@ -110,12 +105,61 @@ 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
}
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")
}
+6 -1
View File
@@ -220,7 +220,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
+126
View File
@@ -0,0 +1,126 @@
// 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"
"io"
"sync"
"github.com/gravitational/trace"
"github.com/gravitational/trace/trail"
"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
promptAppMFA func(ctx context.Context, in *api.PromptMFARequest) (*api.PromptMFAResponse, error)
}
// 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,
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)
if err != nil {
return nil, trace.Wrap(err)
}
// 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.promptMFA(ctx, chal, runOpts)
respC <- libmfa.MFAGoroutineResponse{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 <- libmfa.MFAGoroutineResponse{Resp: resp, Err: trace.Wrap(err, "Webauthn authentication failed")}
}()
}
}
return libmfa.HandleMFAPromptGoroutines(ctx, spawnGoroutines)
}
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) 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,
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
}
@@ -35,6 +35,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
@@ -88,3 +90,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;
}
@@ -207,6 +207,10 @@ function createService(logger: Logger): {
}
);
},
promptMFA: () => {
// TODO (joerger): Handle MFA prompt with totp/webauthn modal.
logger.info('Received prompt mfa request');
},
};
return { service, subscribeToTshdEvent };