diff --git a/integration/helpers/usercreds.go b/integration/helpers/usercreds.go index 5699a1548ddf5..84b7264958a11 100644 --- a/integration/helpers/usercreds.go +++ b/integration/helpers/usercreds.go @@ -98,10 +98,17 @@ type UserCredsRequest struct { RouteToCluster string // SourceIP is an optional source IP to use in SSH certs SourceIP string + // TTL is an optional TTL for the certs. Defaults to one hour. + TTL time.Duration } // GenerateUserCreds generates key to be used by client func GenerateUserCreds(req UserCredsRequest) (*UserCreds, error) { + ttl := req.TTL + if ttl == 0 { + ttl = time.Hour + } + priv, err := testauthority.New().GeneratePrivateKey() if err != nil { return nil, trace.Wrap(err) @@ -109,7 +116,7 @@ func GenerateUserCreds(req UserCredsRequest) (*UserCreds, error) { a := req.Process.GetAuthServer() sshPub := ssh.MarshalAuthorizedKey(priv.SSHPublicKey()) sshCert, x509Cert, err := a.GenerateUserTestCerts( - sshPub, req.Username, time.Hour, constants.CertificateFormatStandard, req.RouteToCluster, req.SourceIP) + sshPub, req.Username, ttl, constants.CertificateFormatStandard, req.RouteToCluster, req.SourceIP) if err != nil { return nil, trace.Wrap(err) } diff --git a/integration/proxy/proxy_test.go b/integration/proxy/proxy_test.go index a96b32b5c0380..c70e419c19b9f 100644 --- a/integration/proxy/proxy_test.go +++ b/integration/proxy/proxy_test.go @@ -814,6 +814,10 @@ func TestALPNSNIProxyDatabaseAccess(t *testing.T) { // Disconnect. require.NoError(t, client.Close()) }) + + t.Run("teleterm gateways cert renewal", func(t *testing.T) { + testTeletermGatewaysCertRenewal(t, pack) + }) } // TestALPNSNIProxyAppAccess tests application access via ALPN SNI proxy service. diff --git a/integration/proxy/teleterm_test.go b/integration/proxy/teleterm_test.go new file mode 100644 index 0000000000000..4fc9793cfed41 --- /dev/null +++ b/integration/proxy/teleterm_test.go @@ -0,0 +1,198 @@ +// Copyright 2022 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 proxy + +import ( + "context" + "net" + "testing" + "time" + + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + dbhelpers "github.com/gravitational/teleport/integration/db" + "github.com/gravitational/teleport/integration/helpers" + libclient "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/srv/db/mysql" + api "github.com/gravitational/teleport/lib/teleterm/api/protogen/golang/v1" + "github.com/gravitational/teleport/lib/teleterm/api/uri" + "github.com/gravitational/teleport/lib/teleterm/clusters" + "github.com/gravitational/teleport/lib/teleterm/daemon" +) + +// testTeletermGatewaysCertRenewal is run from within TestALPNSNIProxyDatabaseAccess to amortize the +// cost of setting up clusters in tests. +func testTeletermGatewaysCertRenewal(t *testing.T, pack *dbhelpers.DatabasePack) { + rootClusterName, _, err := net.SplitHostPort(pack.Root.Cluster.Web) + require.NoError(t, err) + + creds, err := helpers.GenerateUserCreds(helpers.UserCredsRequest{ + Process: pack.Root.Cluster.Process, + Username: pack.Root.User.GetName(), + }) + require.NoError(t, err) + + t.Run("root cluster", func(t *testing.T) { + t.Parallel() + + databaseURI := uri.NewClusterURI(rootClusterName). + AppendDB(pack.Root.MysqlService.Name) + + testGatewayCertRenewal(t, pack, creds, databaseURI) + }) + t.Run("leaf cluster", func(t *testing.T) { + t.Parallel() + + leafClusterName := pack.Leaf.Cluster.Secrets.SiteName + databaseURI := uri.NewClusterURI(rootClusterName). + AppendLeafCluster(leafClusterName). + AppendDB(pack.Leaf.MysqlService.Name) + + testGatewayCertRenewal(t, pack, creds, databaseURI) + }) +} + +func testGatewayCertRenewal(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers.UserCreds, databaseURI uri.ResourceURI) { + tc, err := pack.Root.Cluster.NewClientWithCreds(helpers.ClientConfig{ + Login: pack.Root.User.GetName(), + Cluster: pack.Root.Cluster.Secrets.SiteName, + }, *creds) + require.NoError(t, err) + // The profile on disk created by NewClientWithCreds doesn't have WebProxyAddr set. + tc.WebProxyAddr = pack.Root.Cluster.Web + tc.SaveProfile(tc.KeysDir, false /* makeCurrent */) + + fakeClock := clockwork.NewFakeClockAt(time.Now()) + + storage, err := clusters.NewStorage(clusters.Config{ + Dir: tc.KeysDir, + InsecureSkipVerify: tc.InsecureSkipVerify, + // Inject a fake clock into clusters.Storage so we can control when the middleware thinks the + // db cert has expired. + Clock: fakeClock, + }) + require.NoError(t, err) + + tshdEventsClient := &mockTSHDEventsClient{ + t: t, + tc: tc, + pack: pack, + callCounts: make(map[string]int), + } + + gatewayCertReissuer := &daemon.GatewayCertReissuer{ + Log: logrus.NewEntry(logrus.StandardLogger()).WithField(trace.Component, "reissuer"), + TSHDEventsClient: tshdEventsClient, + } + + daemonService, err := daemon.New(daemon.Config{ + Storage: storage, + CreateTshdEventsClientCredsFunc: func() (grpc.DialOption, error) { + return grpc.WithTransportCredentials(insecure.NewCredentials()), nil + }, + GatewayCertReissuer: gatewayCertReissuer, + }) + require.NoError(t, err) + t.Cleanup(func() { + daemonService.Stop() + }) + + // Here the test setup ends and actual test code starts. + + gateway, err := daemonService.CreateGateway(context.Background(), daemon.CreateGatewayParams{ + TargetURI: databaseURI.String(), + TargetUser: "root", + }) + require.NoError(t, err) + + // Open a new connection. + client, err := mysql.MakeTestClientWithoutTLS( + net.JoinHostPort(gateway.LocalAddress(), gateway.LocalPort()), + gateway.RouteToDatabase()) + require.NoError(t, err) + + // Execute a query. + result, err := client.Execute("select 1") + require.NoError(t, err) + require.Equal(t, mysql.TestQueryResponse, result) + + // Disconnect. + require.NoError(t, client.Close()) + + // Advance the fake clock to simulate the db cert expiry inside the middleware. + fakeClock.Advance(time.Hour * 48) + // Overwrite user certs with expired ones to simulate the user cert expiry. + expiredCreds, err := helpers.GenerateUserCreds(helpers.UserCredsRequest{ + Process: pack.Root.Cluster.Process, + Username: pack.Root.User.GetName(), + TTL: -time.Hour, + }) + require.NoError(t, err) + helpers.SetupUserCreds(tc, pack.Root.Cluster.Config.Proxy.SSHAddr.Addr, *expiredCreds) + + // Open a new connection. + // This should trigger the relogin flow. The middleware will notice that the db cert has expired + // and then it will attempt to reissue the db cert using an expired user cert. + // The mocked tshdEventsClient will issue a valid user cert, save it to disk, and the middleware + // will let the connection through. + client, err = mysql.MakeTestClientWithoutTLS( + net.JoinHostPort(gateway.LocalAddress(), gateway.LocalPort()), + gateway.RouteToDatabase()) + require.NoError(t, err) + + // Execute a query. + result, err = client.Execute("select 1") + require.NoError(t, err) + require.Equal(t, mysql.TestQueryResponse, result) + + // Disconnect. + require.NoError(t, client.Close()) + + require.Equal(t, 1, tshdEventsClient.callCounts["Relogin"], + "Unexpected number of calls to TSHDEventsClient.Relogin") + require.Equal(t, 0, tshdEventsClient.callCounts["SendNotification"], + "Unexpected number of calls to TSHDEventsClient.SendNotification") +} + +type mockTSHDEventsClient struct { + t *testing.T + tc *libclient.TeleportClient + pack *dbhelpers.DatabasePack + callCounts map[string]int +} + +// Relogin simulates the act of the user logging in again in the Electron app by replacing the user +// cert on disk with a valid one. +func (c *mockTSHDEventsClient) Relogin(context.Context, *api.ReloginRequest, ...grpc.CallOption) (*api.ReloginResponse, error) { + c.callCounts["Relogin"]++ + creds, err := helpers.GenerateUserCreds(helpers.UserCredsRequest{ + Process: c.pack.Root.Cluster.Process, + Username: c.pack.Root.User.GetName(), + }) + require.NoError(c.t, err) + helpers.SetupUserCreds(c.tc, c.pack.Root.Cluster.Config.Proxy.SSHAddr.Addr, *creds) + + return &api.ReloginResponse{}, nil +} + +func (c *mockTSHDEventsClient) SendNotification(context.Context, *api.SendNotificationRequest, ...grpc.CallOption) (*api.SendNotificationResponse, error) { + c.callCounts["SendNotification"]++ + return &api.SendNotificationResponse{}, nil +} diff --git a/lib/teleterm/api/proto/v1/tshd_events_service.proto b/lib/teleterm/api/proto/v1/tshd_events_service.proto index 87baa0523cd5d..8020e752d7765 100644 --- a/lib/teleterm/api/proto/v1/tshd_events_service.proto +++ b/lib/teleterm/api/proto/v1/tshd_events_service.proto @@ -19,17 +19,55 @@ package teleport.terminal.v1; option go_package = "github.com/gravitational/teleport/lib/teleterm/v1"; // TshdEventsService is served by the Electron app. The tsh daemon calls this service to notify the -// app about actions that happen outside of the app itself. For example, when the user tries to -// connect to a gateway served by the daemon but the cert has since expired and needs to be -// reissued. +// app about actions that happen outside of the app itself. service TshdEventsService { - // Test is an RPC that's used to demonstrate how the implementation of a tshd event may look like - // from the beginning till the end. - // TODO(ravicious): Remove this once we add an actual RPC to tshd events service. - rpc Test(TestRequest) returns (TestResponse); + // Relogin makes the Electron app display a login modal for the specific root cluster. The request + // returns a response after the relogin procedure has been successfully finished. + rpc Relogin(ReloginRequest) returns (ReloginResponse); + // SendNotification causes the Electron app to display a notification in the UI. The request + // accepts a specific message rather than a generic string so that the Electron is in control as + // to what message is displayed and how exactly it looks. + rpc SendNotification(SendNotificationRequest) returns (SendNotificationResponse); } -message TestRequest { - string foo = 1; +// Relogin + +message ReloginRequest { + string root_cluster_uri = 1; + oneof reason { + GatewayCertExpired gateway_cert_expired = 2; + } +} + +// GatewayCertExpired is given as the reason when a database client attempts to make a connection +// through the gateway, the gateway middleware notices that the db cert has expired and tries to +// connect to the cluster to reissue the cert, but fails because the user cert has expired as well. +// +// At that point in order to let the connection through, tshd needs the Electron app to refresh the +// user cert by asking the user to log in again. +message GatewayCertExpired { + string gateway_uri = 1; + string target_uri = 2; } -message TestResponse {} + +message ReloginResponse {} + +// SendNotification + +message SendNotificationRequest { + oneof subject { + CannotProxyGatewayConnection cannot_proxy_gateway_connection = 1; + } +} + +// CannotProxyGatewayConnection is the subject when the middleware used by the gateway encounters an +// unrecoverable error and cannot let the connection through. The middleware code is executed within +// a separate goroutine so if the error wasn't passed to the Electron app, it would have been +// visible only in the logs. +message CannotProxyGatewayConnection { + string gateway_uri = 1; + string target_uri = 2; + string error = 3; +} + +message SendNotificationResponse {} diff --git a/lib/teleterm/api/protogen/golang/v1/tshd_events_service.pb.go b/lib/teleterm/api/protogen/golang/v1/tshd_events_service.pb.go index 84b5507f604ee..fe4c5b8b729c8 100644 --- a/lib/teleterm/api/protogen/golang/v1/tshd_events_service.pb.go +++ b/lib/teleterm/api/protogen/golang/v1/tshd_events_service.pb.go @@ -34,16 +34,20 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -type TestRequest struct { +type ReloginRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Foo string `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"` + RootClusterUri string `protobuf:"bytes,1,opt,name=root_cluster_uri,json=rootClusterUri,proto3" json:"root_cluster_uri,omitempty"` + // Types that are assignable to Reason: + // + // *ReloginRequest_GatewayCertExpired + Reason isReloginRequest_Reason `protobuf_oneof:"reason"` } -func (x *TestRequest) Reset() { - *x = TestRequest{} +func (x *ReloginRequest) Reset() { + *x = ReloginRequest{} if protoimpl.UnsafeEnabled { mi := &file_v1_tshd_events_service_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -51,13 +55,13 @@ func (x *TestRequest) Reset() { } } -func (x *TestRequest) String() string { +func (x *ReloginRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TestRequest) ProtoMessage() {} +func (*ReloginRequest) ProtoMessage() {} -func (x *TestRequest) ProtoReflect() protoreflect.Message { +func (x *ReloginRequest) ProtoReflect() protoreflect.Message { mi := &file_v1_tshd_events_service_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -69,26 +73,59 @@ func (x *TestRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TestRequest.ProtoReflect.Descriptor instead. -func (*TestRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ReloginRequest.ProtoReflect.Descriptor instead. +func (*ReloginRequest) Descriptor() ([]byte, []int) { return file_v1_tshd_events_service_proto_rawDescGZIP(), []int{0} } -func (x *TestRequest) GetFoo() string { +func (x *ReloginRequest) GetRootClusterUri() string { if x != nil { - return x.Foo + return x.RootClusterUri } return "" } -type TestResponse struct { +func (m *ReloginRequest) GetReason() isReloginRequest_Reason { + if m != nil { + return m.Reason + } + return nil +} + +func (x *ReloginRequest) GetGatewayCertExpired() *GatewayCertExpired { + if x, ok := x.GetReason().(*ReloginRequest_GatewayCertExpired); ok { + return x.GatewayCertExpired + } + return nil +} + +type isReloginRequest_Reason interface { + isReloginRequest_Reason() +} + +type ReloginRequest_GatewayCertExpired struct { + GatewayCertExpired *GatewayCertExpired `protobuf:"bytes,2,opt,name=gateway_cert_expired,json=gatewayCertExpired,proto3,oneof"` +} + +func (*ReloginRequest_GatewayCertExpired) isReloginRequest_Reason() {} + +// GatewayCertExpired is given as the reason when a database client attempts to make a connection +// through the gateway, the gateway middleware notices that the db cert has expired and tries to +// connect to the cluster to reissue the cert, but fails because the user cert has expired as well. +// +// At that point in order to let the connection through, tshd needs the Electron app to refresh the +// user cert by asking the user to log in again. +type GatewayCertExpired struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + GatewayUri string `protobuf:"bytes,1,opt,name=gateway_uri,json=gatewayUri,proto3" json:"gateway_uri,omitempty"` + TargetUri string `protobuf:"bytes,2,opt,name=target_uri,json=targetUri,proto3" json:"target_uri,omitempty"` } -func (x *TestResponse) Reset() { - *x = TestResponse{} +func (x *GatewayCertExpired) Reset() { + *x = GatewayCertExpired{} if protoimpl.UnsafeEnabled { mi := &file_v1_tshd_events_service_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -96,13 +133,13 @@ func (x *TestResponse) Reset() { } } -func (x *TestResponse) String() string { +func (x *GatewayCertExpired) String() string { return protoimpl.X.MessageStringOf(x) } -func (*TestResponse) ProtoMessage() {} +func (*GatewayCertExpired) ProtoMessage() {} -func (x *TestResponse) ProtoReflect() protoreflect.Message { +func (x *GatewayCertExpired) ProtoReflect() protoreflect.Message { mi := &file_v1_tshd_events_service_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -114,27 +151,292 @@ func (x *TestResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use TestResponse.ProtoReflect.Descriptor instead. -func (*TestResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use GatewayCertExpired.ProtoReflect.Descriptor instead. +func (*GatewayCertExpired) Descriptor() ([]byte, []int) { return file_v1_tshd_events_service_proto_rawDescGZIP(), []int{1} } +func (x *GatewayCertExpired) GetGatewayUri() string { + if x != nil { + return x.GatewayUri + } + return "" +} + +func (x *GatewayCertExpired) GetTargetUri() string { + if x != nil { + return x.TargetUri + } + return "" +} + +type ReloginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ReloginResponse) Reset() { + *x = ReloginResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_tshd_events_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReloginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReloginResponse) ProtoMessage() {} + +func (x *ReloginResponse) ProtoReflect() protoreflect.Message { + mi := &file_v1_tshd_events_service_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReloginResponse.ProtoReflect.Descriptor instead. +func (*ReloginResponse) Descriptor() ([]byte, []int) { + return file_v1_tshd_events_service_proto_rawDescGZIP(), []int{2} +} + +type SendNotificationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Subject: + // + // *SendNotificationRequest_CannotProxyGatewayConnection + Subject isSendNotificationRequest_Subject `protobuf_oneof:"subject"` +} + +func (x *SendNotificationRequest) Reset() { + *x = SendNotificationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_tshd_events_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendNotificationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendNotificationRequest) ProtoMessage() {} + +func (x *SendNotificationRequest) ProtoReflect() protoreflect.Message { + mi := &file_v1_tshd_events_service_proto_msgTypes[3] + 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 SendNotificationRequest.ProtoReflect.Descriptor instead. +func (*SendNotificationRequest) Descriptor() ([]byte, []int) { + return file_v1_tshd_events_service_proto_rawDescGZIP(), []int{3} +} + +func (m *SendNotificationRequest) GetSubject() isSendNotificationRequest_Subject { + if m != nil { + return m.Subject + } + return nil +} + +func (x *SendNotificationRequest) GetCannotProxyGatewayConnection() *CannotProxyGatewayConnection { + if x, ok := x.GetSubject().(*SendNotificationRequest_CannotProxyGatewayConnection); ok { + return x.CannotProxyGatewayConnection + } + return nil +} + +type isSendNotificationRequest_Subject interface { + isSendNotificationRequest_Subject() +} + +type SendNotificationRequest_CannotProxyGatewayConnection struct { + CannotProxyGatewayConnection *CannotProxyGatewayConnection `protobuf:"bytes,1,opt,name=cannot_proxy_gateway_connection,json=cannotProxyGatewayConnection,proto3,oneof"` +} + +func (*SendNotificationRequest_CannotProxyGatewayConnection) isSendNotificationRequest_Subject() {} + +// CannotProxyGatewayConnection is the subject when the middleware used by the gateway encounters an +// unrecoverable error and cannot let the connection through. The middleware code is executed within +// a separate goroutine so if the error wasn't passed to the Electron app, it would have been +// visible only in the logs. +type CannotProxyGatewayConnection struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + GatewayUri string `protobuf:"bytes,1,opt,name=gateway_uri,json=gatewayUri,proto3" json:"gateway_uri,omitempty"` + TargetUri string `protobuf:"bytes,2,opt,name=target_uri,json=targetUri,proto3" json:"target_uri,omitempty"` + Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *CannotProxyGatewayConnection) Reset() { + *x = CannotProxyGatewayConnection{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_tshd_events_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CannotProxyGatewayConnection) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CannotProxyGatewayConnection) ProtoMessage() {} + +func (x *CannotProxyGatewayConnection) ProtoReflect() protoreflect.Message { + mi := &file_v1_tshd_events_service_proto_msgTypes[4] + 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 CannotProxyGatewayConnection.ProtoReflect.Descriptor instead. +func (*CannotProxyGatewayConnection) Descriptor() ([]byte, []int) { + return file_v1_tshd_events_service_proto_rawDescGZIP(), []int{4} +} + +func (x *CannotProxyGatewayConnection) GetGatewayUri() string { + if x != nil { + return x.GatewayUri + } + return "" +} + +func (x *CannotProxyGatewayConnection) GetTargetUri() string { + if x != nil { + return x.TargetUri + } + return "" +} + +func (x *CannotProxyGatewayConnection) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type SendNotificationResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SendNotificationResponse) Reset() { + *x = SendNotificationResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_tshd_events_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SendNotificationResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendNotificationResponse) ProtoMessage() {} + +func (x *SendNotificationResponse) ProtoReflect() protoreflect.Message { + mi := &file_v1_tshd_events_service_proto_msgTypes[5] + 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 SendNotificationResponse.ProtoReflect.Descriptor instead. +func (*SendNotificationResponse) Descriptor() ([]byte, []int) { + return file_v1_tshd_events_service_proto_rawDescGZIP(), []int{5} +} + var File_v1_tshd_events_service_proto protoreflect.FileDescriptor var file_v1_tshd_events_service_proto_rawDesc = []byte{ 0x0a, 0x1c, 0x76, 0x31, 0x2f, 0x74, 0x73, 0x68, 0x64, 0x5f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, - 0x6c, 0x2e, 0x76, 0x31, 0x22, 0x1f, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x66, 0x6f, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x66, 0x6f, 0x6f, 0x22, 0x0e, 0x0a, 0x0c, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x62, 0x0a, 0x11, 0x54, 0x73, 0x68, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4d, 0x0a, 0x04, 0x54, 0x65, - 0x73, 0x74, 0x12, 0x21, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x65, - 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, + 0x6c, 0x2e, 0x76, 0x31, 0x22, 0xa2, 0x01, 0x0a, 0x0e, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, + 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, 0x5c, 0x0a, 0x14, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x63, 0x65, 0x72, + 0x74, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x28, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x65, 0x72, 0x6d, 0x69, + 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x65, + 0x72, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x48, 0x00, 0x52, 0x12, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x43, 0x65, 0x72, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x42, + 0x08, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x54, 0x0a, 0x12, 0x47, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x43, 0x65, 0x72, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x55, 0x72, 0x69, + 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x55, 0x72, 0x69, 0x22, + 0x11, 0x0a, 0x0f, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0xa1, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x7b, + 0x0a, 0x1f, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x67, + 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x1c, 0x63, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x09, 0x0a, 0x07, 0x73, + 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x74, 0x0a, 0x1c, 0x43, 0x61, 0x6e, 0x6e, 0x6f, 0x74, + 0x50, 0x72, 0x6f, 0x78, 0x79, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, + 0x79, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x67, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x55, 0x72, 0x69, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x55, 0x72, 0x69, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x1a, 0x0a, 0x18, + 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xde, 0x01, 0x0a, 0x11, 0x54, 0x73, 0x68, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x56, + 0x0a, 0x07, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x24, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x25, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x65, 0x72, 0x6d, 0x69, + 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x10, 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 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, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 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, 0x42, 0x33, 0x5a, 0x31, 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, 0x6c, 0x69, 0x62, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x2f, 0x76, 0x31, 0x62, 0x06, @@ -153,19 +455,27 @@ func file_v1_tshd_events_service_proto_rawDescGZIP() []byte { return file_v1_tshd_events_service_proto_rawDescData } -var file_v1_tshd_events_service_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_v1_tshd_events_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_v1_tshd_events_service_proto_goTypes = []interface{}{ - (*TestRequest)(nil), // 0: teleport.terminal.v1.TestRequest - (*TestResponse)(nil), // 1: teleport.terminal.v1.TestResponse + (*ReloginRequest)(nil), // 0: teleport.terminal.v1.ReloginRequest + (*GatewayCertExpired)(nil), // 1: teleport.terminal.v1.GatewayCertExpired + (*ReloginResponse)(nil), // 2: teleport.terminal.v1.ReloginResponse + (*SendNotificationRequest)(nil), // 3: teleport.terminal.v1.SendNotificationRequest + (*CannotProxyGatewayConnection)(nil), // 4: teleport.terminal.v1.CannotProxyGatewayConnection + (*SendNotificationResponse)(nil), // 5: teleport.terminal.v1.SendNotificationResponse } var file_v1_tshd_events_service_proto_depIdxs = []int32{ - 0, // 0: teleport.terminal.v1.TshdEventsService.Test:input_type -> teleport.terminal.v1.TestRequest - 1, // 1: teleport.terminal.v1.TshdEventsService.Test:output_type -> teleport.terminal.v1.TestResponse - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 1, // 0: teleport.terminal.v1.ReloginRequest.gateway_cert_expired:type_name -> teleport.terminal.v1.GatewayCertExpired + 4, // 1: teleport.terminal.v1.SendNotificationRequest.cannot_proxy_gateway_connection:type_name -> teleport.terminal.v1.CannotProxyGatewayConnection + 0, // 2: teleport.terminal.v1.TshdEventsService.Relogin:input_type -> teleport.terminal.v1.ReloginRequest + 3, // 3: teleport.terminal.v1.TshdEventsService.SendNotification:input_type -> teleport.terminal.v1.SendNotificationRequest + 2, // 4: teleport.terminal.v1.TshdEventsService.Relogin:output_type -> teleport.terminal.v1.ReloginResponse + 5, // 5: teleport.terminal.v1.TshdEventsService.SendNotification:output_type -> teleport.terminal.v1.SendNotificationResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] 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 } func init() { file_v1_tshd_events_service_proto_init() } @@ -175,7 +485,7 @@ func file_v1_tshd_events_service_proto_init() { } if !protoimpl.UnsafeEnabled { file_v1_tshd_events_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestRequest); i { + switch v := v.(*ReloginRequest); i { case 0: return &v.state case 1: @@ -187,7 +497,43 @@ func file_v1_tshd_events_service_proto_init() { } } file_v1_tshd_events_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestResponse); i { + switch v := v.(*GatewayCertExpired); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_tshd_events_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReloginResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_tshd_events_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendNotificationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_tshd_events_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CannotProxyGatewayConnection); i { case 0: return &v.state case 1: @@ -198,6 +544,24 @@ func file_v1_tshd_events_service_proto_init() { return nil } } + file_v1_tshd_events_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendNotificationResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_v1_tshd_events_service_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*ReloginRequest_GatewayCertExpired)(nil), + } + file_v1_tshd_events_service_proto_msgTypes[3].OneofWrappers = []interface{}{ + (*SendNotificationRequest_CannotProxyGatewayConnection)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -205,7 +569,7 @@ func file_v1_tshd_events_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_tshd_events_service_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 6, NumExtensions: 0, NumServices: 1, }, diff --git a/lib/teleterm/api/protogen/golang/v1/tshd_events_service_grpc.pb.go b/lib/teleterm/api/protogen/golang/v1/tshd_events_service_grpc.pb.go index 04ceeb62d01db..9956001ca81a3 100644 --- a/lib/teleterm/api/protogen/golang/v1/tshd_events_service_grpc.pb.go +++ b/lib/teleterm/api/protogen/golang/v1/tshd_events_service_grpc.pb.go @@ -22,10 +22,13 @@ const _ = grpc.SupportPackageIsVersion7 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type TshdEventsServiceClient interface { - // Test is an RPC that's used to demonstrate how the implementation of a tshd event may look like - // from the beginning till the end. - // TODO(ravicious): Remove this once we add an actual RPC to tshd events service. - Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) + // Relogin makes the Electron app display a login modal for the specific root cluster. The request + // returns a response after the relogin procedure has been successfully finished. + Relogin(ctx context.Context, in *ReloginRequest, opts ...grpc.CallOption) (*ReloginResponse, error) + // SendNotification causes the Electron app to display a notification in the UI. The request + // accepts a specific message rather than a generic string so that the Electron is in control as + // to what message is displayed and how exactly it looks. + SendNotification(ctx context.Context, in *SendNotificationRequest, opts ...grpc.CallOption) (*SendNotificationResponse, error) } type tshdEventsServiceClient struct { @@ -36,9 +39,18 @@ func NewTshdEventsServiceClient(cc grpc.ClientConnInterface) TshdEventsServiceCl return &tshdEventsServiceClient{cc} } -func (c *tshdEventsServiceClient) Test(ctx context.Context, in *TestRequest, opts ...grpc.CallOption) (*TestResponse, error) { - out := new(TestResponse) - err := c.cc.Invoke(ctx, "/teleport.terminal.v1.TshdEventsService/Test", in, out, opts...) +func (c *tshdEventsServiceClient) Relogin(ctx context.Context, in *ReloginRequest, opts ...grpc.CallOption) (*ReloginResponse, error) { + out := new(ReloginResponse) + err := c.cc.Invoke(ctx, "/teleport.terminal.v1.TshdEventsService/Relogin", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *tshdEventsServiceClient) SendNotification(ctx context.Context, in *SendNotificationRequest, opts ...grpc.CallOption) (*SendNotificationResponse, error) { + out := new(SendNotificationResponse) + err := c.cc.Invoke(ctx, "/teleport.terminal.v1.TshdEventsService/SendNotification", in, out, opts...) if err != nil { return nil, err } @@ -49,10 +61,13 @@ func (c *tshdEventsServiceClient) Test(ctx context.Context, in *TestRequest, opt // All implementations must embed UnimplementedTshdEventsServiceServer // for forward compatibility type TshdEventsServiceServer interface { - // Test is an RPC that's used to demonstrate how the implementation of a tshd event may look like - // from the beginning till the end. - // TODO(ravicious): Remove this once we add an actual RPC to tshd events service. - Test(context.Context, *TestRequest) (*TestResponse, error) + // Relogin makes the Electron app display a login modal for the specific root cluster. The request + // returns a response after the relogin procedure has been successfully finished. + Relogin(context.Context, *ReloginRequest) (*ReloginResponse, error) + // SendNotification causes the Electron app to display a notification in the UI. The request + // accepts a specific message rather than a generic string so that the Electron is in control as + // to what message is displayed and how exactly it looks. + SendNotification(context.Context, *SendNotificationRequest) (*SendNotificationResponse, error) mustEmbedUnimplementedTshdEventsServiceServer() } @@ -60,8 +75,11 @@ type TshdEventsServiceServer interface { type UnimplementedTshdEventsServiceServer struct { } -func (UnimplementedTshdEventsServiceServer) Test(context.Context, *TestRequest) (*TestResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Test not implemented") +func (UnimplementedTshdEventsServiceServer) Relogin(context.Context, *ReloginRequest) (*ReloginResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Relogin not implemented") +} +func (UnimplementedTshdEventsServiceServer) SendNotification(context.Context, *SendNotificationRequest) (*SendNotificationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendNotification not implemented") } func (UnimplementedTshdEventsServiceServer) mustEmbedUnimplementedTshdEventsServiceServer() {} @@ -76,20 +94,38 @@ func RegisterTshdEventsServiceServer(s grpc.ServiceRegistrar, srv TshdEventsServ s.RegisterService(&TshdEventsService_ServiceDesc, srv) } -func _TshdEventsService_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(TestRequest) +func _TshdEventsService_Relogin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReloginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TshdEventsServiceServer).Relogin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/teleport.terminal.v1.TshdEventsService/Relogin", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TshdEventsServiceServer).Relogin(ctx, req.(*ReloginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TshdEventsService_SendNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendNotificationRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(TshdEventsServiceServer).Test(ctx, in) + return srv.(TshdEventsServiceServer).SendNotification(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/teleport.terminal.v1.TshdEventsService/Test", + FullMethod: "/teleport.terminal.v1.TshdEventsService/SendNotification", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(TshdEventsServiceServer).Test(ctx, req.(*TestRequest)) + return srv.(TshdEventsServiceServer).SendNotification(ctx, req.(*SendNotificationRequest)) } return interceptor(ctx, in, info, handler) } @@ -102,8 +138,12 @@ var TshdEventsService_ServiceDesc = grpc.ServiceDesc{ HandlerType: (*TshdEventsServiceServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "Test", - Handler: _TshdEventsService_Test_Handler, + MethodName: "Relogin", + Handler: _TshdEventsService_Relogin_Handler, + }, + { + MethodName: "SendNotification", + Handler: _TshdEventsService_SendNotification_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/lib/teleterm/api/protogen/js/v1/tshd_events_service_grpc_pb.d.ts b/lib/teleterm/api/protogen/js/v1/tshd_events_service_grpc_pb.d.ts index 6e10cb03db1bc..6f2f5780f2009 100644 --- a/lib/teleterm/api/protogen/js/v1/tshd_events_service_grpc_pb.d.ts +++ b/lib/teleterm/api/protogen/js/v1/tshd_events_service_grpc_pb.d.ts @@ -8,34 +8,51 @@ import * as grpc from "grpc"; import * as v1_tshd_events_service_pb from "../v1/tshd_events_service_pb"; interface ITshdEventsServiceService extends grpc.ServiceDefinition { - test: ITshdEventsServiceService_ITest; + relogin: ITshdEventsServiceService_IRelogin; + sendNotification: ITshdEventsServiceService_ISendNotification; } -interface ITshdEventsServiceService_ITest extends grpc.MethodDefinition { - path: "/teleport.terminal.v1.TshdEventsService/Test"; +interface ITshdEventsServiceService_IRelogin extends grpc.MethodDefinition { + path: "/teleport.terminal.v1.TshdEventsService/Relogin"; requestStream: false; responseStream: false; - requestSerialize: grpc.serialize; - requestDeserialize: grpc.deserialize; - responseSerialize: grpc.serialize; - responseDeserialize: grpc.deserialize; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} +interface ITshdEventsServiceService_ISendNotification extends grpc.MethodDefinition { + path: "/teleport.terminal.v1.TshdEventsService/SendNotification"; + requestStream: false; + responseStream: false; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; } export const TshdEventsServiceService: ITshdEventsServiceService; export interface ITshdEventsServiceServer { - test: grpc.handleUnaryCall; + relogin: grpc.handleUnaryCall; + sendNotification: grpc.handleUnaryCall; } export interface ITshdEventsServiceClient { - test(request: v1_tshd_events_service_pb.TestRequest, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.TestResponse) => void): grpc.ClientUnaryCall; - test(request: v1_tshd_events_service_pb.TestRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.TestResponse) => void): grpc.ClientUnaryCall; - test(request: v1_tshd_events_service_pb.TestRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.TestResponse) => void): grpc.ClientUnaryCall; + relogin(request: v1_tshd_events_service_pb.ReloginRequest, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.ReloginResponse) => void): grpc.ClientUnaryCall; + relogin(request: v1_tshd_events_service_pb.ReloginRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.ReloginResponse) => void): grpc.ClientUnaryCall; + relogin(request: v1_tshd_events_service_pb.ReloginRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.ReloginResponse) => void): grpc.ClientUnaryCall; + sendNotification(request: v1_tshd_events_service_pb.SendNotificationRequest, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.SendNotificationResponse) => void): grpc.ClientUnaryCall; + sendNotification(request: v1_tshd_events_service_pb.SendNotificationRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.SendNotificationResponse) => void): grpc.ClientUnaryCall; + sendNotification(request: v1_tshd_events_service_pb.SendNotificationRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.SendNotificationResponse) => void): grpc.ClientUnaryCall; } export class TshdEventsServiceClient extends grpc.Client implements ITshdEventsServiceClient { constructor(address: string, credentials: grpc.ChannelCredentials, options?: object); - public test(request: v1_tshd_events_service_pb.TestRequest, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.TestResponse) => void): grpc.ClientUnaryCall; - public test(request: v1_tshd_events_service_pb.TestRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.TestResponse) => void): grpc.ClientUnaryCall; - public test(request: v1_tshd_events_service_pb.TestRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.TestResponse) => void): grpc.ClientUnaryCall; + public relogin(request: v1_tshd_events_service_pb.ReloginRequest, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.ReloginResponse) => void): grpc.ClientUnaryCall; + public relogin(request: v1_tshd_events_service_pb.ReloginRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.ReloginResponse) => void): grpc.ClientUnaryCall; + public relogin(request: v1_tshd_events_service_pb.ReloginRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.ReloginResponse) => void): grpc.ClientUnaryCall; + public sendNotification(request: v1_tshd_events_service_pb.SendNotificationRequest, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.SendNotificationResponse) => void): grpc.ClientUnaryCall; + public sendNotification(request: v1_tshd_events_service_pb.SendNotificationRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.SendNotificationResponse) => void): grpc.ClientUnaryCall; + public sendNotification(request: v1_tshd_events_service_pb.SendNotificationRequest, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: v1_tshd_events_service_pb.SendNotificationResponse) => void): grpc.ClientUnaryCall; } diff --git a/lib/teleterm/api/protogen/js/v1/tshd_events_service_grpc_pb.js b/lib/teleterm/api/protogen/js/v1/tshd_events_service_grpc_pb.js index 6859c723eef5e..9ef264c208979 100644 --- a/lib/teleterm/api/protogen/js/v1/tshd_events_service_grpc_pb.js +++ b/lib/teleterm/api/protogen/js/v1/tshd_events_service_grpc_pb.js @@ -19,47 +19,80 @@ var grpc = require('@grpc/grpc-js'); var v1_tshd_events_service_pb = require('../v1/tshd_events_service_pb.js'); -function serialize_teleport_terminal_v1_TestRequest(arg) { - if (!(arg instanceof v1_tshd_events_service_pb.TestRequest)) { - throw new Error('Expected argument of type teleport.terminal.v1.TestRequest'); +function serialize_teleport_terminal_v1_ReloginRequest(arg) { + if (!(arg instanceof v1_tshd_events_service_pb.ReloginRequest)) { + throw new Error('Expected argument of type teleport.terminal.v1.ReloginRequest'); } return Buffer.from(arg.serializeBinary()); } -function deserialize_teleport_terminal_v1_TestRequest(buffer_arg) { - return v1_tshd_events_service_pb.TestRequest.deserializeBinary(new Uint8Array(buffer_arg)); +function deserialize_teleport_terminal_v1_ReloginRequest(buffer_arg) { + return v1_tshd_events_service_pb.ReloginRequest.deserializeBinary(new Uint8Array(buffer_arg)); } -function serialize_teleport_terminal_v1_TestResponse(arg) { - if (!(arg instanceof v1_tshd_events_service_pb.TestResponse)) { - throw new Error('Expected argument of type teleport.terminal.v1.TestResponse'); +function serialize_teleport_terminal_v1_ReloginResponse(arg) { + if (!(arg instanceof v1_tshd_events_service_pb.ReloginResponse)) { + throw new Error('Expected argument of type teleport.terminal.v1.ReloginResponse'); } return Buffer.from(arg.serializeBinary()); } -function deserialize_teleport_terminal_v1_TestResponse(buffer_arg) { - return v1_tshd_events_service_pb.TestResponse.deserializeBinary(new Uint8Array(buffer_arg)); +function deserialize_teleport_terminal_v1_ReloginResponse(buffer_arg) { + return v1_tshd_events_service_pb.ReloginResponse.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_teleport_terminal_v1_SendNotificationRequest(arg) { + if (!(arg instanceof v1_tshd_events_service_pb.SendNotificationRequest)) { + throw new Error('Expected argument of type teleport.terminal.v1.SendNotificationRequest'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_teleport_terminal_v1_SendNotificationRequest(buffer_arg) { + return v1_tshd_events_service_pb.SendNotificationRequest.deserializeBinary(new Uint8Array(buffer_arg)); +} + +function serialize_teleport_terminal_v1_SendNotificationResponse(arg) { + if (!(arg instanceof v1_tshd_events_service_pb.SendNotificationResponse)) { + throw new Error('Expected argument of type teleport.terminal.v1.SendNotificationResponse'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_teleport_terminal_v1_SendNotificationResponse(buffer_arg) { + return v1_tshd_events_service_pb.SendNotificationResponse.deserializeBinary(new Uint8Array(buffer_arg)); } // TshdEventsService is served by the Electron app. The tsh daemon calls this service to notify the -// app about actions that happen outside of the app itself. For example, when the user tries to -// connect to a gateway served by the daemon but the cert has since expired and needs to be -// reissued. +// app about actions that happen outside of the app itself. var TshdEventsServiceService = exports.TshdEventsServiceService = { - // Test is an RPC that's used to demonstrate how the implementation of a tshd event may look like -// from the beginning till the end. -// TODO(ravicious): Remove this once we add an actual RPC to tshd events service. -test: { - path: '/teleport.terminal.v1.TshdEventsService/Test', + // Relogin makes the Electron app display a login modal for the specific root cluster. The request +// returns a response after the relogin procedure has been successfully finished. +relogin: { + path: '/teleport.terminal.v1.TshdEventsService/Relogin', + requestStream: false, + responseStream: false, + requestType: v1_tshd_events_service_pb.ReloginRequest, + responseType: v1_tshd_events_service_pb.ReloginResponse, + requestSerialize: serialize_teleport_terminal_v1_ReloginRequest, + requestDeserialize: deserialize_teleport_terminal_v1_ReloginRequest, + responseSerialize: serialize_teleport_terminal_v1_ReloginResponse, + responseDeserialize: deserialize_teleport_terminal_v1_ReloginResponse, + }, + // SendNotification causes the Electron app to display a notification in the UI. The request +// accepts a specific message rather than a generic string so that the Electron is in control as +// to what message is displayed and how exactly it looks. +sendNotification: { + path: '/teleport.terminal.v1.TshdEventsService/SendNotification', requestStream: false, responseStream: false, - requestType: v1_tshd_events_service_pb.TestRequest, - responseType: v1_tshd_events_service_pb.TestResponse, - requestSerialize: serialize_teleport_terminal_v1_TestRequest, - requestDeserialize: deserialize_teleport_terminal_v1_TestRequest, - responseSerialize: serialize_teleport_terminal_v1_TestResponse, - responseDeserialize: deserialize_teleport_terminal_v1_TestResponse, + requestType: v1_tshd_events_service_pb.SendNotificationRequest, + responseType: v1_tshd_events_service_pb.SendNotificationResponse, + requestSerialize: serialize_teleport_terminal_v1_SendNotificationRequest, + requestDeserialize: deserialize_teleport_terminal_v1_SendNotificationRequest, + responseSerialize: serialize_teleport_terminal_v1_SendNotificationResponse, + responseDeserialize: deserialize_teleport_terminal_v1_SendNotificationResponse, }, }; diff --git a/lib/teleterm/api/protogen/js/v1/tshd_events_service_pb.d.ts b/lib/teleterm/api/protogen/js/v1/tshd_events_service_pb.d.ts index 18c43d1d13e0c..1c1f59330ddf6 100644 --- a/lib/teleterm/api/protogen/js/v1/tshd_events_service_pb.d.ts +++ b/lib/teleterm/api/protogen/js/v1/tshd_events_service_pb.d.ts @@ -6,40 +6,162 @@ import * as jspb from "google-protobuf"; -export class TestRequest extends jspb.Message { - getFoo(): string; - setFoo(value: string): TestRequest; +export class ReloginRequest extends jspb.Message { + getRootClusterUri(): string; + setRootClusterUri(value: string): ReloginRequest; + + + hasGatewayCertExpired(): boolean; + clearGatewayCertExpired(): void; + getGatewayCertExpired(): GatewayCertExpired | undefined; + setGatewayCertExpired(value?: GatewayCertExpired): ReloginRequest; + + + getReasonCase(): ReloginRequest.ReasonCase; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): ReloginRequest.AsObject; + static toObject(includeInstance: boolean, msg: ReloginRequest): ReloginRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: ReloginRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): ReloginRequest; + static deserializeBinaryFromReader(message: ReloginRequest, reader: jspb.BinaryReader): ReloginRequest; +} + +export namespace ReloginRequest { + export type AsObject = { + rootClusterUri: string, + gatewayCertExpired?: GatewayCertExpired.AsObject, + } + + export enum ReasonCase { + REASON_NOT_SET = 0, + + GATEWAY_CERT_EXPIRED = 2, + + } + +} + +export class GatewayCertExpired extends jspb.Message { + getGatewayUri(): string; + setGatewayUri(value: string): GatewayCertExpired; + + getTargetUri(): string; + setTargetUri(value: string): GatewayCertExpired; + + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): GatewayCertExpired.AsObject; + static toObject(includeInstance: boolean, msg: GatewayCertExpired): GatewayCertExpired.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: GatewayCertExpired, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): GatewayCertExpired; + static deserializeBinaryFromReader(message: GatewayCertExpired, reader: jspb.BinaryReader): GatewayCertExpired; +} + +export namespace GatewayCertExpired { + export type AsObject = { + gatewayUri: string, + targetUri: string, + } +} + +export class ReloginResponse extends jspb.Message { + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): ReloginResponse.AsObject; + static toObject(includeInstance: boolean, msg: ReloginResponse): ReloginResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: ReloginResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): ReloginResponse; + static deserializeBinaryFromReader(message: ReloginResponse, reader: jspb.BinaryReader): ReloginResponse; +} + +export namespace ReloginResponse { + export type AsObject = { + } +} + +export class SendNotificationRequest extends jspb.Message { + + hasCannotProxyGatewayConnection(): boolean; + clearCannotProxyGatewayConnection(): void; + getCannotProxyGatewayConnection(): CannotProxyGatewayConnection | undefined; + setCannotProxyGatewayConnection(value?: CannotProxyGatewayConnection): SendNotificationRequest; + + + getSubjectCase(): SendNotificationRequest.SubjectCase; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SendNotificationRequest.AsObject; + static toObject(includeInstance: boolean, msg: SendNotificationRequest): SendNotificationRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: SendNotificationRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SendNotificationRequest; + static deserializeBinaryFromReader(message: SendNotificationRequest, reader: jspb.BinaryReader): SendNotificationRequest; +} + +export namespace SendNotificationRequest { + export type AsObject = { + cannotProxyGatewayConnection?: CannotProxyGatewayConnection.AsObject, + } + + export enum SubjectCase { + SUBJECT_NOT_SET = 0, + + CANNOT_PROXY_GATEWAY_CONNECTION = 1, + + } + +} + +export class CannotProxyGatewayConnection extends jspb.Message { + getGatewayUri(): string; + setGatewayUri(value: string): CannotProxyGatewayConnection; + + getTargetUri(): string; + setTargetUri(value: string): CannotProxyGatewayConnection; + + getError(): string; + setError(value: string): CannotProxyGatewayConnection; serializeBinary(): Uint8Array; - toObject(includeInstance?: boolean): TestRequest.AsObject; - static toObject(includeInstance: boolean, msg: TestRequest): TestRequest.AsObject; + toObject(includeInstance?: boolean): CannotProxyGatewayConnection.AsObject; + static toObject(includeInstance: boolean, msg: CannotProxyGatewayConnection): CannotProxyGatewayConnection.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; - static serializeBinaryToWriter(message: TestRequest, writer: jspb.BinaryWriter): void; - static deserializeBinary(bytes: Uint8Array): TestRequest; - static deserializeBinaryFromReader(message: TestRequest, reader: jspb.BinaryReader): TestRequest; + static serializeBinaryToWriter(message: CannotProxyGatewayConnection, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): CannotProxyGatewayConnection; + static deserializeBinaryFromReader(message: CannotProxyGatewayConnection, reader: jspb.BinaryReader): CannotProxyGatewayConnection; } -export namespace TestRequest { +export namespace CannotProxyGatewayConnection { export type AsObject = { - foo: string, + gatewayUri: string, + targetUri: string, + error: string, } } -export class TestResponse extends jspb.Message { +export class SendNotificationResponse extends jspb.Message { serializeBinary(): Uint8Array; - toObject(includeInstance?: boolean): TestResponse.AsObject; - static toObject(includeInstance: boolean, msg: TestResponse): TestResponse.AsObject; + toObject(includeInstance?: boolean): SendNotificationResponse.AsObject; + static toObject(includeInstance: boolean, msg: SendNotificationResponse): SendNotificationResponse.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; - static serializeBinaryToWriter(message: TestResponse, writer: jspb.BinaryWriter): void; - static deserializeBinary(bytes: Uint8Array): TestResponse; - static deserializeBinaryFromReader(message: TestResponse, reader: jspb.BinaryReader): TestResponse; + static serializeBinaryToWriter(message: SendNotificationResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SendNotificationResponse; + static deserializeBinaryFromReader(message: SendNotificationResponse, reader: jspb.BinaryReader): SendNotificationResponse; } -export namespace TestResponse { +export namespace SendNotificationResponse { export type AsObject = { } } diff --git a/lib/teleterm/api/protogen/js/v1/tshd_events_service_pb.js b/lib/teleterm/api/protogen/js/v1/tshd_events_service_pb.js index b7416effdb326..972ae81ea18e7 100644 --- a/lib/teleterm/api/protogen/js/v1/tshd_events_service_pb.js +++ b/lib/teleterm/api/protogen/js/v1/tshd_events_service_pb.js @@ -15,8 +15,14 @@ var jspb = require('google-protobuf'); var goog = jspb; var global = (function() { return this || window || global || self || Function('return this')(); }).call(null); -goog.exportSymbol('proto.teleport.terminal.v1.TestRequest', null, global); -goog.exportSymbol('proto.teleport.terminal.v1.TestResponse', null, global); +goog.exportSymbol('proto.teleport.terminal.v1.CannotProxyGatewayConnection', null, global); +goog.exportSymbol('proto.teleport.terminal.v1.GatewayCertExpired', null, global); +goog.exportSymbol('proto.teleport.terminal.v1.ReloginRequest', null, global); +goog.exportSymbol('proto.teleport.terminal.v1.ReloginRequest.ReasonCase', null, global); +goog.exportSymbol('proto.teleport.terminal.v1.ReloginResponse', null, global); +goog.exportSymbol('proto.teleport.terminal.v1.SendNotificationRequest', null, global); +goog.exportSymbol('proto.teleport.terminal.v1.SendNotificationRequest.SubjectCase', null, global); +goog.exportSymbol('proto.teleport.terminal.v1.SendNotificationResponse', null, global); /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -27,16 +33,100 @@ goog.exportSymbol('proto.teleport.terminal.v1.TestResponse', null, global); * @extends {jspb.Message} * @constructor */ -proto.teleport.terminal.v1.TestRequest = function(opt_data) { +proto.teleport.terminal.v1.ReloginRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.teleport.terminal.v1.ReloginRequest.oneofGroups_); +}; +goog.inherits(proto.teleport.terminal.v1.ReloginRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.teleport.terminal.v1.ReloginRequest.displayName = 'proto.teleport.terminal.v1.ReloginRequest'; +} +/** + * 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.terminal.v1.GatewayCertExpired = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.teleport.terminal.v1.GatewayCertExpired, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.teleport.terminal.v1.GatewayCertExpired.displayName = 'proto.teleport.terminal.v1.GatewayCertExpired'; +} +/** + * 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.terminal.v1.ReloginResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.teleport.terminal.v1.ReloginResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.teleport.terminal.v1.ReloginResponse.displayName = 'proto.teleport.terminal.v1.ReloginResponse'; +} +/** + * 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.terminal.v1.SendNotificationRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.teleport.terminal.v1.SendNotificationRequest.oneofGroups_); +}; +goog.inherits(proto.teleport.terminal.v1.SendNotificationRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.teleport.terminal.v1.SendNotificationRequest.displayName = 'proto.teleport.terminal.v1.SendNotificationRequest'; +} +/** + * 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.terminal.v1.CannotProxyGatewayConnection = function(opt_data) { jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.teleport.terminal.v1.TestRequest, jspb.Message); +goog.inherits(proto.teleport.terminal.v1.CannotProxyGatewayConnection, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.teleport.terminal.v1.TestRequest.displayName = 'proto.teleport.terminal.v1.TestRequest'; + proto.teleport.terminal.v1.CannotProxyGatewayConnection.displayName = 'proto.teleport.terminal.v1.CannotProxyGatewayConnection'; } /** * Generated by JsPbCodeGenerator. @@ -48,19 +138,225 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.teleport.terminal.v1.TestResponse = function(opt_data) { +proto.teleport.terminal.v1.SendNotificationResponse = function(opt_data) { jspb.Message.initialize(this, opt_data, 0, -1, null, null); }; -goog.inherits(proto.teleport.terminal.v1.TestResponse, jspb.Message); +goog.inherits(proto.teleport.terminal.v1.SendNotificationResponse, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.teleport.terminal.v1.TestResponse.displayName = 'proto.teleport.terminal.v1.TestResponse'; + proto.teleport.terminal.v1.SendNotificationResponse.displayName = 'proto.teleport.terminal.v1.SendNotificationResponse'; +} + +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.teleport.terminal.v1.ReloginRequest.oneofGroups_ = [[2]]; + +/** + * @enum {number} + */ +proto.teleport.terminal.v1.ReloginRequest.ReasonCase = { + REASON_NOT_SET: 0, + GATEWAY_CERT_EXPIRED: 2 +}; + +/** + * @return {proto.teleport.terminal.v1.ReloginRequest.ReasonCase} + */ +proto.teleport.terminal.v1.ReloginRequest.prototype.getReasonCase = function() { + return /** @type {proto.teleport.terminal.v1.ReloginRequest.ReasonCase} */(jspb.Message.computeOneofCase(this, proto.teleport.terminal.v1.ReloginRequest.oneofGroups_[0])); +}; + + + +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.terminal.v1.ReloginRequest.prototype.toObject = function(opt_includeInstance) { + return proto.teleport.terminal.v1.ReloginRequest.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.terminal.v1.ReloginRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.terminal.v1.ReloginRequest.toObject = function(includeInstance, msg) { + var f, obj = { + rootClusterUri: jspb.Message.getFieldWithDefault(msg, 1, ""), + gatewayCertExpired: (f = msg.getGatewayCertExpired()) && proto.teleport.terminal.v1.GatewayCertExpired.toObject(includeInstance, f) + }; + + 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.terminal.v1.ReloginRequest} + */ +proto.teleport.terminal.v1.ReloginRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.teleport.terminal.v1.ReloginRequest; + return proto.teleport.terminal.v1.ReloginRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.teleport.terminal.v1.ReloginRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.teleport.terminal.v1.ReloginRequest} + */ +proto.teleport.terminal.v1.ReloginRequest.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 = new proto.teleport.terminal.v1.GatewayCertExpired; + reader.readMessage(value,proto.teleport.terminal.v1.GatewayCertExpired.deserializeBinaryFromReader); + msg.setGatewayCertExpired(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.teleport.terminal.v1.ReloginRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.teleport.terminal.v1.ReloginRequest.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.terminal.v1.ReloginRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.terminal.v1.ReloginRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getRootClusterUri(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getGatewayCertExpired(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.teleport.terminal.v1.GatewayCertExpired.serializeBinaryToWriter + ); + } +}; + + +/** + * optional string root_cluster_uri = 1; + * @return {string} + */ +proto.teleport.terminal.v1.ReloginRequest.prototype.getRootClusterUri = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.terminal.v1.ReloginRequest} returns this + */ +proto.teleport.terminal.v1.ReloginRequest.prototype.setRootClusterUri = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional GatewayCertExpired gateway_cert_expired = 2; + * @return {?proto.teleport.terminal.v1.GatewayCertExpired} + */ +proto.teleport.terminal.v1.ReloginRequest.prototype.getGatewayCertExpired = function() { + return /** @type{?proto.teleport.terminal.v1.GatewayCertExpired} */ ( + jspb.Message.getWrapperField(this, proto.teleport.terminal.v1.GatewayCertExpired, 2)); +}; + + +/** + * @param {?proto.teleport.terminal.v1.GatewayCertExpired|undefined} value + * @return {!proto.teleport.terminal.v1.ReloginRequest} returns this +*/ +proto.teleport.terminal.v1.ReloginRequest.prototype.setGatewayCertExpired = function(value) { + return jspb.Message.setOneofWrapperField(this, 2, proto.teleport.terminal.v1.ReloginRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.teleport.terminal.v1.ReloginRequest} returns this + */ +proto.teleport.terminal.v1.ReloginRequest.prototype.clearGatewayCertExpired = function() { + return this.setGatewayCertExpired(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.teleport.terminal.v1.ReloginRequest.prototype.hasGatewayCertExpired = function() { + return jspb.Message.getField(this, 2) != null; +}; + + + + if (jspb.Message.GENERATE_TO_OBJECT) { /** @@ -75,8 +371,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.teleport.terminal.v1.TestRequest.prototype.toObject = function(opt_includeInstance) { - return proto.teleport.terminal.v1.TestRequest.toObject(opt_includeInstance, this); +proto.teleport.terminal.v1.GatewayCertExpired.prototype.toObject = function(opt_includeInstance) { + return proto.teleport.terminal.v1.GatewayCertExpired.toObject(opt_includeInstance, this); }; @@ -85,13 +381,14 @@ proto.teleport.terminal.v1.TestRequest.prototype.toObject = function(opt_include * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.teleport.terminal.v1.TestRequest} msg The msg instance to transform. + * @param {!proto.teleport.terminal.v1.GatewayCertExpired} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.teleport.terminal.v1.TestRequest.toObject = function(includeInstance, msg) { +proto.teleport.terminal.v1.GatewayCertExpired.toObject = function(includeInstance, msg) { var f, obj = { - foo: jspb.Message.getFieldWithDefault(msg, 1, "") + gatewayUri: jspb.Message.getFieldWithDefault(msg, 1, ""), + targetUri: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -105,23 +402,23 @@ proto.teleport.terminal.v1.TestRequest.toObject = function(includeInstance, msg) /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.teleport.terminal.v1.TestRequest} + * @return {!proto.teleport.terminal.v1.GatewayCertExpired} */ -proto.teleport.terminal.v1.TestRequest.deserializeBinary = function(bytes) { +proto.teleport.terminal.v1.GatewayCertExpired.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.teleport.terminal.v1.TestRequest; - return proto.teleport.terminal.v1.TestRequest.deserializeBinaryFromReader(msg, reader); + var msg = new proto.teleport.terminal.v1.GatewayCertExpired; + return proto.teleport.terminal.v1.GatewayCertExpired.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.teleport.terminal.v1.TestRequest} msg The message object to deserialize into. + * @param {!proto.teleport.terminal.v1.GatewayCertExpired} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.teleport.terminal.v1.TestRequest} + * @return {!proto.teleport.terminal.v1.GatewayCertExpired} */ -proto.teleport.terminal.v1.TestRequest.deserializeBinaryFromReader = function(msg, reader) { +proto.teleport.terminal.v1.GatewayCertExpired.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -130,7 +427,11 @@ proto.teleport.terminal.v1.TestRequest.deserializeBinaryFromReader = function(ms switch (field) { case 1: var value = /** @type {string} */ (reader.readString()); - msg.setFoo(value); + msg.setGatewayUri(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setTargetUri(value); break; default: reader.skipField(); @@ -145,9 +446,9 @@ proto.teleport.terminal.v1.TestRequest.deserializeBinaryFromReader = function(ms * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.teleport.terminal.v1.TestRequest.prototype.serializeBinary = function() { +proto.teleport.terminal.v1.GatewayCertExpired.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.teleport.terminal.v1.TestRequest.serializeBinaryToWriter(this, writer); + proto.teleport.terminal.v1.GatewayCertExpired.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -155,40 +456,532 @@ proto.teleport.terminal.v1.TestRequest.prototype.serializeBinary = function() { /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.teleport.terminal.v1.TestRequest} message + * @param {!proto.teleport.terminal.v1.GatewayCertExpired} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.teleport.terminal.v1.TestRequest.serializeBinaryToWriter = function(message, writer) { +proto.teleport.terminal.v1.GatewayCertExpired.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getFoo(); + f = message.getGatewayUri(); if (f.length > 0) { writer.writeString( 1, f ); } + f = message.getTargetUri(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } }; /** - * optional string foo = 1; + * optional string gateway_uri = 1; * @return {string} */ -proto.teleport.terminal.v1.TestRequest.prototype.getFoo = function() { +proto.teleport.terminal.v1.GatewayCertExpired.prototype.getGatewayUri = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); }; /** * @param {string} value - * @return {!proto.teleport.terminal.v1.TestRequest} returns this + * @return {!proto.teleport.terminal.v1.GatewayCertExpired} returns this */ -proto.teleport.terminal.v1.TestRequest.prototype.setFoo = function(value) { +proto.teleport.terminal.v1.GatewayCertExpired.prototype.setGatewayUri = function(value) { return jspb.Message.setProto3StringField(this, 1, value); }; +/** + * optional string target_uri = 2; + * @return {string} + */ +proto.teleport.terminal.v1.GatewayCertExpired.prototype.getTargetUri = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.terminal.v1.GatewayCertExpired} returns this + */ +proto.teleport.terminal.v1.GatewayCertExpired.prototype.setTargetUri = function(value) { + return jspb.Message.setProto3StringField(this, 2, 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.terminal.v1.ReloginResponse.prototype.toObject = function(opt_includeInstance) { + return proto.teleport.terminal.v1.ReloginResponse.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.terminal.v1.ReloginResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.terminal.v1.ReloginResponse.toObject = function(includeInstance, msg) { + var f, obj = { + + }; + + 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.terminal.v1.ReloginResponse} + */ +proto.teleport.terminal.v1.ReloginResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.teleport.terminal.v1.ReloginResponse; + return proto.teleport.terminal.v1.ReloginResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.teleport.terminal.v1.ReloginResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.teleport.terminal.v1.ReloginResponse} + */ +proto.teleport.terminal.v1.ReloginResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.teleport.terminal.v1.ReloginResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.teleport.terminal.v1.ReloginResponse.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.terminal.v1.ReloginResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.terminal.v1.ReloginResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; +}; + + + +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.teleport.terminal.v1.SendNotificationRequest.oneofGroups_ = [[1]]; + +/** + * @enum {number} + */ +proto.teleport.terminal.v1.SendNotificationRequest.SubjectCase = { + SUBJECT_NOT_SET: 0, + CANNOT_PROXY_GATEWAY_CONNECTION: 1 +}; + +/** + * @return {proto.teleport.terminal.v1.SendNotificationRequest.SubjectCase} + */ +proto.teleport.terminal.v1.SendNotificationRequest.prototype.getSubjectCase = function() { + return /** @type {proto.teleport.terminal.v1.SendNotificationRequest.SubjectCase} */(jspb.Message.computeOneofCase(this, proto.teleport.terminal.v1.SendNotificationRequest.oneofGroups_[0])); +}; + + + +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.terminal.v1.SendNotificationRequest.prototype.toObject = function(opt_includeInstance) { + return proto.teleport.terminal.v1.SendNotificationRequest.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.terminal.v1.SendNotificationRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.terminal.v1.SendNotificationRequest.toObject = function(includeInstance, msg) { + var f, obj = { + cannotProxyGatewayConnection: (f = msg.getCannotProxyGatewayConnection()) && proto.teleport.terminal.v1.CannotProxyGatewayConnection.toObject(includeInstance, f) + }; + + 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.terminal.v1.SendNotificationRequest} + */ +proto.teleport.terminal.v1.SendNotificationRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.teleport.terminal.v1.SendNotificationRequest; + return proto.teleport.terminal.v1.SendNotificationRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.teleport.terminal.v1.SendNotificationRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.teleport.terminal.v1.SendNotificationRequest} + */ +proto.teleport.terminal.v1.SendNotificationRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.teleport.terminal.v1.CannotProxyGatewayConnection; + reader.readMessage(value,proto.teleport.terminal.v1.CannotProxyGatewayConnection.deserializeBinaryFromReader); + msg.setCannotProxyGatewayConnection(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.teleport.terminal.v1.SendNotificationRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.teleport.terminal.v1.SendNotificationRequest.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.terminal.v1.SendNotificationRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.terminal.v1.SendNotificationRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getCannotProxyGatewayConnection(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.teleport.terminal.v1.CannotProxyGatewayConnection.serializeBinaryToWriter + ); + } +}; + + +/** + * optional CannotProxyGatewayConnection cannot_proxy_gateway_connection = 1; + * @return {?proto.teleport.terminal.v1.CannotProxyGatewayConnection} + */ +proto.teleport.terminal.v1.SendNotificationRequest.prototype.getCannotProxyGatewayConnection = function() { + return /** @type{?proto.teleport.terminal.v1.CannotProxyGatewayConnection} */ ( + jspb.Message.getWrapperField(this, proto.teleport.terminal.v1.CannotProxyGatewayConnection, 1)); +}; + + +/** + * @param {?proto.teleport.terminal.v1.CannotProxyGatewayConnection|undefined} value + * @return {!proto.teleport.terminal.v1.SendNotificationRequest} returns this +*/ +proto.teleport.terminal.v1.SendNotificationRequest.prototype.setCannotProxyGatewayConnection = function(value) { + return jspb.Message.setOneofWrapperField(this, 1, proto.teleport.terminal.v1.SendNotificationRequest.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.teleport.terminal.v1.SendNotificationRequest} returns this + */ +proto.teleport.terminal.v1.SendNotificationRequest.prototype.clearCannotProxyGatewayConnection = function() { + return this.setCannotProxyGatewayConnection(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.teleport.terminal.v1.SendNotificationRequest.prototype.hasCannotProxyGatewayConnection = function() { + return jspb.Message.getField(this, 1) != null; +}; + + + + + +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.terminal.v1.CannotProxyGatewayConnection.prototype.toObject = function(opt_includeInstance) { + return proto.teleport.terminal.v1.CannotProxyGatewayConnection.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.terminal.v1.CannotProxyGatewayConnection} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.toObject = function(includeInstance, msg) { + var f, obj = { + gatewayUri: jspb.Message.getFieldWithDefault(msg, 1, ""), + targetUri: jspb.Message.getFieldWithDefault(msg, 2, ""), + error: jspb.Message.getFieldWithDefault(msg, 3, "") + }; + + 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.terminal.v1.CannotProxyGatewayConnection} + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.teleport.terminal.v1.CannotProxyGatewayConnection; + return proto.teleport.terminal.v1.CannotProxyGatewayConnection.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.teleport.terminal.v1.CannotProxyGatewayConnection} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.teleport.terminal.v1.CannotProxyGatewayConnection} + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.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.setGatewayUri(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setTargetUri(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setError(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.teleport.terminal.v1.CannotProxyGatewayConnection.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.terminal.v1.CannotProxyGatewayConnection} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getGatewayUri(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getTargetUri(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getError(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } +}; + + +/** + * optional string gateway_uri = 1; + * @return {string} + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.prototype.getGatewayUri = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.terminal.v1.CannotProxyGatewayConnection} returns this + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.prototype.setGatewayUri = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string target_uri = 2; + * @return {string} + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.prototype.getTargetUri = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.terminal.v1.CannotProxyGatewayConnection} returns this + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.prototype.setTargetUri = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string error = 3; + * @return {string} + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.prototype.getError = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.teleport.terminal.v1.CannotProxyGatewayConnection} returns this + */ +proto.teleport.terminal.v1.CannotProxyGatewayConnection.prototype.setError = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + @@ -205,8 +998,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.teleport.terminal.v1.TestResponse.prototype.toObject = function(opt_includeInstance) { - return proto.teleport.terminal.v1.TestResponse.toObject(opt_includeInstance, this); +proto.teleport.terminal.v1.SendNotificationResponse.prototype.toObject = function(opt_includeInstance) { + return proto.teleport.terminal.v1.SendNotificationResponse.toObject(opt_includeInstance, this); }; @@ -215,11 +1008,11 @@ proto.teleport.terminal.v1.TestResponse.prototype.toObject = function(opt_includ * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.teleport.terminal.v1.TestResponse} msg The msg instance to transform. + * @param {!proto.teleport.terminal.v1.SendNotificationResponse} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.teleport.terminal.v1.TestResponse.toObject = function(includeInstance, msg) { +proto.teleport.terminal.v1.SendNotificationResponse.toObject = function(includeInstance, msg) { var f, obj = { }; @@ -235,23 +1028,23 @@ proto.teleport.terminal.v1.TestResponse.toObject = function(includeInstance, msg /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.teleport.terminal.v1.TestResponse} + * @return {!proto.teleport.terminal.v1.SendNotificationResponse} */ -proto.teleport.terminal.v1.TestResponse.deserializeBinary = function(bytes) { +proto.teleport.terminal.v1.SendNotificationResponse.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.teleport.terminal.v1.TestResponse; - return proto.teleport.terminal.v1.TestResponse.deserializeBinaryFromReader(msg, reader); + var msg = new proto.teleport.terminal.v1.SendNotificationResponse; + return proto.teleport.terminal.v1.SendNotificationResponse.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.teleport.terminal.v1.TestResponse} msg The message object to deserialize into. + * @param {!proto.teleport.terminal.v1.SendNotificationResponse} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.teleport.terminal.v1.TestResponse} + * @return {!proto.teleport.terminal.v1.SendNotificationResponse} */ -proto.teleport.terminal.v1.TestResponse.deserializeBinaryFromReader = function(msg, reader) { +proto.teleport.terminal.v1.SendNotificationResponse.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -271,9 +1064,9 @@ proto.teleport.terminal.v1.TestResponse.deserializeBinaryFromReader = function(m * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.teleport.terminal.v1.TestResponse.prototype.serializeBinary = function() { +proto.teleport.terminal.v1.SendNotificationResponse.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.teleport.terminal.v1.TestResponse.serializeBinaryToWriter(this, writer); + proto.teleport.terminal.v1.SendNotificationResponse.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -281,11 +1074,11 @@ proto.teleport.terminal.v1.TestResponse.prototype.serializeBinary = function() { /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.teleport.terminal.v1.TestResponse} message + * @param {!proto.teleport.terminal.v1.SendNotificationResponse} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.teleport.terminal.v1.TestResponse.serializeBinaryToWriter = function(message, writer) { +proto.teleport.terminal.v1.SendNotificationResponse.serializeBinaryToWriter = function(message, writer) { var f = undefined; }; diff --git a/lib/teleterm/api/uri/uri.go b/lib/teleterm/api/uri/uri.go index a0107606f1e51..4cee39e19af2b 100644 --- a/lib/teleterm/api/uri/uri.go +++ b/lib/teleterm/api/uri/uri.go @@ -109,6 +109,11 @@ func (r ResourceURI) GetDbName() string { return "" } +// GetRootClusterURI trims the existing ResourceURI into a URI that points solely at the root cluster. +func (r ResourceURI) GetRootClusterURI() ResourceURI { + return NewClusterURI(r.GetProfileName()) +} + // AppendServer appends server segment to the URI func (r ResourceURI) AppendServer(id string) ResourceURI { r.path = fmt.Sprintf("%v/servers/%v", r.path, id) diff --git a/lib/teleterm/api/uri/uri_test.go b/lib/teleterm/api/uri/uri_test.go index 04273a1c68c9f..2fc89a99857e9 100644 --- a/lib/teleterm/api/uri/uri_test.go +++ b/lib/teleterm/api/uri/uri_test.go @@ -129,3 +129,44 @@ func TestGetDbName(t *testing.T) { }) } } + +func TestGetRootClusterURI(t *testing.T) { + tests := []struct { + name string + in uri.ResourceURI + out uri.ResourceURI + }{ + { + name: "noop on root cluster URI", + in: uri.NewClusterURI("foo"), + out: uri.NewClusterURI("foo"), + }, + { + name: "trims root cluster resource URI", + in: uri.NewClusterURI("foo").AppendDB("postgres"), + out: uri.NewClusterURI("foo"), + }, + { + name: "trims leaf cluster URI", + in: uri.NewClusterURI("foo").AppendLeafCluster("bar"), + out: uri.NewClusterURI("foo"), + }, + { + name: "trims leaf cluster resource URI", + in: uri.NewClusterURI("foo").AppendLeafCluster("bar").AppendDB("postgres"), + out: uri.NewClusterURI("foo"), + }, + { + name: "returns empty URI if given a gateway URI", + in: uri.NewGatewayURI("quux"), + out: uri.NewClusterURI(""), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + out := tt.in.GetRootClusterURI() + require.Equal(t, tt.out, out) + }) + } +} diff --git a/lib/teleterm/daemon/config.go b/lib/teleterm/daemon/config.go index d1a764f6cccd1..2930846652e47 100644 --- a/lib/teleterm/daemon/config.go +++ b/lib/teleterm/daemon/config.go @@ -37,6 +37,7 @@ type Config struct { // Electron app. This is to ensure that the server public key is written to the disk under the // expected location by the time we get around to creating the client. CreateTshdEventsClientCredsFunc CreateTshdEventsClientCredsFunc + GatewayCertReissuer *GatewayCertReissuer } type CreateTshdEventsClientCredsFunc func() (grpc.DialOption, error) @@ -59,5 +60,11 @@ func (c *Config) CheckAndSetDefaults() error { c.Log = logrus.NewEntry(logrus.StandardLogger()).WithField(trace.Component, "daemon") } + if c.GatewayCertReissuer == nil { + c.GatewayCertReissuer = &GatewayCertReissuer{ + Log: c.Log, + } + } + return nil } diff --git a/lib/teleterm/daemon/daemon.go b/lib/teleterm/daemon/daemon.go index 0fc20339ebc86..3bbff28df16b6 100644 --- a/lib/teleterm/daemon/daemon.go +++ b/lib/teleterm/daemon/daemon.go @@ -17,6 +17,7 @@ package daemon import ( "context" "sync" + "time" "github.com/gravitational/trace" "google.golang.org/grpc" @@ -28,6 +29,14 @@ import ( "github.com/gravitational/teleport/lib/teleterm/gateway" ) +const ( + // tshdEventsTimeout is the maximum amount of time the gRPC client managed by the tshd daemon will + // wait for a response from the tshd events server managed by the Electron app. This timeout + // should be used for quick one-off calls where the client doesn't need the server or the user to + // perform any additional work, such as the SendNotification RPC. + tshdEventsTimeout = time.Second +) + // New creates an instance of Daemon service func New(cfg Config) (*Service, error) { if err := cfg.CheckAndSetDefaults(); err != nil { @@ -104,9 +113,10 @@ func (s *Service) RemoveCluster(ctx context.Context, uri string) error { return nil } -// ResolveCluster resolves a cluster by URI and returns -// information stored in the profile along with a TeleportClient. -// It will not include detailed information returned from the web/auth servers +// ResolveCluster resolves a cluster by URI by reading data stored on disk in the profile. +// +// It doesn't make network requests so the returned clusters.Cluster will not include full +// information returned from the web/auth servers. func (s *Service) ResolveCluster(uri string) (*clusters.Cluster, error) { cluster, err := s.cfg.Storage.GetByResourceURI(uri) if err != nil { @@ -116,7 +126,7 @@ func (s *Service) ResolveCluster(uri string) (*clusters.Cluster, error) { return cluster, nil } -// GetCluster returns cluster information +// GetCluster returns full cluster information. It makes a request to the auth server. func (s *Service) GetCluster(ctx context.Context, uri string) (*clusters.Cluster, error) { cluster, err := s.ResolveCluster(uri) if err != nil { @@ -174,6 +184,7 @@ func (s *Service) createGateway(ctx context.Context, params CreateGatewayParams) LocalPort: params.LocalPort, CLICommandProvider: cliCommandProvider, TCPPortAllocator: s.cfg.TCPPortAllocator, + OnExpiredCert: s.onExpiredGatewayCert, } gateway, err := s.cfg.GatewayCreator.CreateGateway(ctx, clusterCreateGatewayParams) @@ -192,6 +203,15 @@ func (s *Service) createGateway(ctx context.Context, params CreateGatewayParams) return gateway, nil } +func (s *Service) onExpiredGatewayCert(ctx context.Context, gateway *gateway.Gateway) error { + cluster, err := s.ResolveCluster(gateway.TargetURI()) + if err != nil { + return trace.Wrap(err) + } + + return trace.Wrap(s.cfg.GatewayCertReissuer.ReissueCert(ctx, gateway, cluster)) +} + // RemoveGateway removes cluster gateway func (s *Service) RemoveGateway(gatewayURI string) error { s.mu.Lock() @@ -572,7 +592,9 @@ func (s *Service) UpdateAndDialTshdEventsServerAddress(serverAddress string) err } client := api.NewTshdEventsServiceClient(conn) - s.tshdEventsClient = client + // If the need arises to reuse the client in other places, + // read https://github.com/gravitational/teleport/pull/17950#discussion_r1039434456 + s.cfg.GatewayCertReissuer.TSHDEventsClient = client return nil } @@ -589,7 +611,8 @@ func (s *Service) TransferFile(ctx context.Context, request *api.FileTransferReq // Service is the daemon service type Service struct { cfg *Config - mu sync.RWMutex + // mu guards gateways and the creation of tshdEventsClient. + mu sync.RWMutex // closeContext is canceled when Service is getting stopped. It is used as a context for the calls // to the tshd events gRPC client. closeContext context.Context @@ -597,11 +620,6 @@ type Service struct { // gateways holds the long-running gateways for resources on different clusters. So far it's been // used mostly for database gateways but it has potential to be used for app access as well. gateways map[string]*gateway.Gateway - // tshdEventsClient is created after UpdateAndDialTshdEventsServerAddress gets called. The startup - // of the whole app is orchestrated in a way which ensures that is the first Service method that - // gets called. This lets other methods in Service assume that tshdEventsClient is available from - // the start, without having to perform nil checks. - tshdEventsClient api.TshdEventsServiceClient } type CreateGatewayParams struct { diff --git a/lib/teleterm/daemon/daemon_test.go b/lib/teleterm/daemon/daemon_test.go index 111c21d9a58ab..a55e65d7ad468 100644 --- a/lib/teleterm/daemon/daemon_test.go +++ b/lib/teleterm/daemon/daemon_test.go @@ -286,9 +286,12 @@ func TestUpdateTshdEventsServerAddress(t *testing.T) { return grpc.WithTransportCredentials(insecure.NewCredentials()), nil } + gatewayCertReissuer := GatewayCertReissuer{Log: storage.Log} + daemon, err := New(Config{ Storage: storage, CreateTshdEventsClientCredsFunc: createTshdEventsClientCredsFunc, + GatewayCertReissuer: &gatewayCertReissuer, }) require.NoError(t, err) @@ -298,7 +301,7 @@ func TestUpdateTshdEventsServerAddress(t *testing.T) { err = daemon.UpdateAndDialTshdEventsServerAddress(ls.Addr().String()) require.NoError(t, err) - require.NotNil(t, daemon.tshdEventsClient) + require.NotNil(t, gatewayCertReissuer.TSHDEventsClient) require.Equal(t, 1, createTshdEventsClientCredsFuncCallCount, "Expected createTshdEventsClientCredsFunc to be called exactly once") } diff --git a/lib/teleterm/daemon/gateway_cert_reissuer.go b/lib/teleterm/daemon/gateway_cert_reissuer.go new file mode 100644 index 0000000000000..f2cbaab8c6dc6 --- /dev/null +++ b/lib/teleterm/daemon/gateway_cert_reissuer.go @@ -0,0 +1,192 @@ +// Copyright 2022 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" + "sync" + "time" + + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/gravitational/teleport/lib/client" + api "github.com/gravitational/teleport/lib/teleterm/api/protogen/golang/v1" + "github.com/gravitational/teleport/lib/teleterm/api/uri" + "github.com/gravitational/teleport/lib/teleterm/gateway" + "github.com/gravitational/teleport/lib/tlsca" +) + +// GatewayCertReissuer is responsible for managing the process of reissuing a db cert for a gateway +// after the db cert expires. +type GatewayCertReissuer struct { + // TSHDEventsClient gets set by daemon.Service behind its mutex. + TSHDEventsClient TSHDEventsClient + // reloginMu is used when a goroutine needs to request a relogin frem the Electron app. Since the + // app can show only one login modal at a time, we need to submit only one request at a time. + reloginMu sync.Mutex + Log *logrus.Entry +} + +// DBCertReissuer lets us pass a mock in tests and clusters.Cluster (which makes calls to the +// cluster) in production code. +type DBCertReissuer interface { + // ReissueDBCerts reaches out to the cluster to get a cert for the specific tlsca.RouteToDatabase + // and saves it to disk. + ReissueDBCerts(context.Context, tlsca.RouteToDatabase) error +} + +// TSHDEventsClient takes only those methods from api.TshdEventsServiceClient that +// GatewayCertReissuer actually needs. It makes mocking the client in tests easier and future-proof. +// +// Refer to [api.TshdEventsServiceClient] for a more detailed documentation. +type TSHDEventsClient interface { + // Relogin makes the Electron app display a login modal. Please refer to + // [api.TshdEventsServiceClient.Relogin] for more details. + Relogin(ctx context.Context, in *api.ReloginRequest, opts ...grpc.CallOption) (*api.ReloginResponse, error) + // SendNotification causes the Electron app to display a notification. Please refer to + // [api.TshdEventsServiceClient.SendNotification] for more details. + SendNotification(ctx context.Context, in *api.SendNotificationRequest, opts ...grpc.CallOption) (*api.SendNotificationResponse, error) +} + +// ReissueCert attempts to contact the cluster to reissue the db cert used by the gateway. If that +// operation fails and the error is resolvable by relogin, ReissueCert tells the Electron app to +// relogin the user. Once that is done, it attempts to reissue the db cert again. +// +// ReissueCert is called by the LocalProxy middleware used by Connect's gateways. The middleware +// calls ReissueCert on an incoming connection to the proxy if the db cert used by the proxy has +// expired. +// +// If the initial call to the cluster fails with an error that is not resolvable by logging in, +// ReissueCert returns with that error. +// +// Any error ReissueCert returns is also forwarded to the Electron app so that it can show an error +// notification. GatewayCertReissuer is typically called from within a goroutine that handles the +// gateway, so without forwarding the error to the app, it would be visible only in the logs. +func (r *GatewayCertReissuer) ReissueCert(ctx context.Context, gateway *gateway.Gateway, dbCertReissuer DBCertReissuer) error { + if err := r.reissueCert(ctx, gateway, dbCertReissuer); err != nil { + r.notifyAppAboutError(ctx, err, gateway) + + // Return the error to the alpn.LocalProxy's middleware. + return trace.Wrap(err) + } + + return nil +} + +func (r *GatewayCertReissuer) reissueCert(ctx context.Context, gateway *gateway.Gateway, dbCertReissuer DBCertReissuer) error { + // Make the first attempt at reissuing the db cert. + // + // It might happen that the db cert has expired but the user cert is still active, allowing us to + // obtain a new db cert without having to relogin first. + // + // This can happen if the user cert was refreshed by anything other than the gateway itself. For + // example, if you execute `tsh ssh` within Connect after your user cert expires or there are two + // gateways that subsequently go through this flow. + err := r.reissueAndReloadGatewayCert(ctx, gateway, dbCertReissuer) + + if err == nil { + return nil + } + + // Do not ask for relogin if the error cannot be resolved with relogin. + if !client.IsErrorResolvableWithRelogin(err) { + return trace.Wrap(err) + } + + clusterURI, err := uri.ParseClusterURI(gateway.TargetURI()) + if err != nil { + return trace.Wrap(err) + } + rootClusterURI := clusterURI.GetRootClusterURI().String() + + err = r.requestReloginFromElectronApp(ctx, + &api.ReloginRequest{ + RootClusterUri: rootClusterURI, + Reason: &api.ReloginRequest_GatewayCertExpired{ + GatewayCertExpired: &api.GatewayCertExpired{ + GatewayUri: gateway.URI().String(), + TargetUri: gateway.TargetURI(), + }, + }, + }) + if err != nil { + return trace.Wrap(err) + } + + err = r.reissueAndReloadGatewayCert(ctx, gateway, dbCertReissuer) + if err != nil { + return trace.Wrap(err) + } + + return nil +} + +func (r *GatewayCertReissuer) reissueAndReloadGatewayCert(ctx context.Context, gateway *gateway.Gateway, dbCertReissuer DBCertReissuer) error { + err := dbCertReissuer.ReissueDBCerts(ctx, gateway.RouteToDatabase()) + if err != nil { + return trace.Wrap(err) + } + + return trace.Wrap(gateway.ReloadCert()) +} + +func (r *GatewayCertReissuer) requestReloginFromElectronApp(ctx context.Context, req *api.ReloginRequest) error { + const reloginUserTimeout = time.Minute + timeoutCtx, cancelTshdEventsCtx := context.WithTimeout(ctx, reloginUserTimeout) + defer cancelTshdEventsCtx() + + // The Electron app cannot display two login modals at the same time, so we have to cut short any + // concurrent relogin requests. + if !r.reloginMu.TryLock() { + return trace.AlreadyExists("another relogin request is in progress") + } + defer r.reloginMu.Unlock() + + _, err := r.TSHDEventsClient.Relogin(timeoutCtx, req) + + if err != nil { + if status.Code(err) == codes.DeadlineExceeded { + return trace.Wrap(err, "the user did not refresh the session within %s", reloginUserTimeout.String()) + } + + return trace.Wrap(err, "could not refresh the session") + } + + return nil +} + +func (r *GatewayCertReissuer) notifyAppAboutError(ctx context.Context, err error, gateway *gateway.Gateway) { + tshdEventsCtx, cancelTshdEventsCtx := context.WithTimeout(ctx, tshdEventsTimeout) + defer cancelTshdEventsCtx() + + _, tshdEventsErr := r.TSHDEventsClient.SendNotification(tshdEventsCtx, + &api.SendNotificationRequest{ + Subject: &api.SendNotificationRequest_CannotProxyGatewayConnection{ + CannotProxyGatewayConnection: &api.CannotProxyGatewayConnection{ + GatewayUri: gateway.URI().String(), + TargetUri: gateway.TargetURI(), + Error: err.Error(), + }, + }, + }) + if tshdEventsErr != nil { + r.Log.WithError(tshdEventsErr).Error( + "Failed to send a notification for an error encountered during OnExpiredCert") + } +} diff --git a/lib/teleterm/daemon/gateway_cert_reissuer_test.go b/lib/teleterm/daemon/gateway_cert_reissuer_test.go new file mode 100644 index 0000000000000..604e22d04bd62 --- /dev/null +++ b/lib/teleterm/daemon/gateway_cert_reissuer_test.go @@ -0,0 +1,210 @@ +// Copyright 2022 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" + "fmt" + "testing" + + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + api "github.com/gravitational/teleport/lib/teleterm/api/protogen/golang/v1" + "github.com/gravitational/teleport/lib/teleterm/api/uri" + "github.com/gravitational/teleport/lib/teleterm/clusters" + "github.com/gravitational/teleport/lib/teleterm/gateway" + "github.com/gravitational/teleport/lib/tlsca" +) + +var log = logrus.WithField(trace.Component, "reissuer") + +func TestReissueCert(t *testing.T) { + t.Parallel() + resolvableErr := trace.Errorf("ssh: cert has expired") + unresolvableErr := trace.AccessDenied("") + concurrentCallErr := trace.AlreadyExists("") + reloginTimeoutErr := status.Error(codes.DeadlineExceeded, "foo") + unknownErr := status.Error(codes.Unknown, "foo") + tests := []struct { + name string + reissueErrs []error + reloginErr error + reissuerOpt func(t *testing.T, reissuer *GatewayCertReissuer) + wantReissueCalls int + wantReloginCalls int + wantNotifyCalls int + wantErr error + wantAddedMessage string + }{ + { + name: "calls DB cert reissuer once if it returns successfully", + reissueErrs: []error{nil}, + wantReissueCalls: 1, + }, + { + name: "calls DB cert reissuer once if it returns error unresolvable with relogin", + reissueErrs: []error{unresolvableErr}, + wantReissueCalls: 1, + wantReloginCalls: 0, + wantNotifyCalls: 1, + wantErr: unresolvableErr, + }, + { + name: "resolves error with relogin and calls DB cert reissuer twice", + reissueErrs: []error{resolvableErr}, + wantReissueCalls: 2, + wantReloginCalls: 1, + wantNotifyCalls: 0, + }, + { + name: "does not allow concurrent relogin calls", + reissueErrs: []error{concurrentCallErr}, + reissuerOpt: func(t *testing.T, reissuer *GatewayCertReissuer) { + t.Helper() + require.True(t, reissuer.reloginMu.TryLock(), "Couldn't lock reloginMu") + }, + wantReissueCalls: 1, + wantReloginCalls: 0, + wantNotifyCalls: 1, + wantErr: concurrentCallErr, + }, + { + name: "adds additional message to error on timeout during relogin", + reissueErrs: []error{resolvableErr}, + reloginErr: reloginTimeoutErr, + wantReissueCalls: 1, + wantReloginCalls: 1, + wantNotifyCalls: 1, + wantErr: reloginTimeoutErr, + wantAddedMessage: "the user did not refresh the session within", + }, + { + name: "adds additional message to error on unexpected error during relogin", + reissueErrs: []error{resolvableErr}, + reloginErr: unknownErr, + wantReissueCalls: 1, + wantReloginCalls: 1, + wantNotifyCalls: 1, + wantErr: unknownErr, + wantAddedMessage: "could not refresh the session", + }, + { + name: "sends notification if second call to reissue certs fails", + reissueErrs: []error{resolvableErr, unresolvableErr}, + wantReissueCalls: 2, + wantReloginCalls: 1, + wantNotifyCalls: 1, + wantErr: unresolvableErr, + }, + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + gateway := mustCreateGateway(ctx, t) + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + tshdEventsClient := &mockTSHDEventsClient{ + callCounts: make(map[string]int), + reloginErr: tt.reloginErr, + } + reissuer := &GatewayCertReissuer{ + Log: log, + TSHDEventsClient: tshdEventsClient, + } + dbCertReissuer := &mockDBCertReissuer{ + returnValuesForSubsequentCalls: tt.reissueErrs, + } + if tt.reissuerOpt != nil { + tt.reissuerOpt(t, reissuer) + } + err := reissuer.ReissueCert(ctx, gateway, dbCertReissuer) + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + require.ErrorContains(t, err, tt.wantAddedMessage) + } else { + require.NoError(t, err) + } + require.Equal(t, tt.wantReissueCalls, dbCertReissuer.callCount, + "Unexpected number of calls to DBCertReissuer.ReissueDBCerts") + require.Equal(t, tt.wantReloginCalls, tshdEventsClient.callCounts["Relogin"], + "Unexpected number of calls to TSHDEventsClient.Relogin") + require.Equal(t, tt.wantNotifyCalls, tshdEventsClient.callCounts["SendNotification"], + "Unexpected number of calls to TSHDEventsClient.SendNotification") + }) + } +} + +func mustCreateGateway(ctx context.Context, t *testing.T) *gateway.Gateway { + t.Helper() + + gatewayCreator := &mockGatewayCreator{t: t} + gateway, err := gatewayCreator.CreateGateway(ctx, clusters.CreateGatewayParams{ + TargetURI: uri.NewClusterURI("foo").AppendDB("postgres").String(), + TargetUser: "alice", + CLICommandProvider: &mockCLICommandProvider{}, + }) + require.NoError(t, err) + return gateway +} + +type mockDBCertReissuer struct { + callCount int + returnValuesForSubsequentCalls []error +} + +func (r *mockDBCertReissuer) ReissueDBCerts(context.Context, tlsca.RouteToDatabase) error { + var err error + if r.callCount < len(r.returnValuesForSubsequentCalls) { + err = r.returnValuesForSubsequentCalls[r.callCount] + } + + r.callCount++ + + return err +} + +type mockCLICommandProvider struct{} + +func (m mockCLICommandProvider) GetCommand(gateway *gateway.Gateway) (string, error) { + command := fmt.Sprintf("%s/%s", gateway.TargetName(), gateway.TargetSubresourceName()) + return command, nil +} + +type mockTSHDEventsClient struct { + callCounts map[string]int + reloginErr error +} + +func (c *mockTSHDEventsClient) Relogin(context.Context, *api.ReloginRequest, ...grpc.CallOption) (*api.ReloginResponse, error) { + c.callCounts["Relogin"]++ + + if c.reloginErr != nil { + return nil, c.reloginErr + } + + return &api.ReloginResponse{}, nil +} + +func (c *mockTSHDEventsClient) SendNotification(context.Context, *api.SendNotificationRequest, ...grpc.CallOption) (*api.SendNotificationResponse, error) { + c.callCounts["SendNotification"]++ + return &api.SendNotificationResponse{}, nil +} diff --git a/lib/teleterm/gateway/local_proxy_middleware.go b/lib/teleterm/gateway/local_proxy_middleware.go index 9db185bba5d60..b163ae99d347c 100644 --- a/lib/teleterm/gateway/local_proxy_middleware.go +++ b/lib/teleterm/gateway/local_proxy_middleware.go @@ -40,8 +40,6 @@ type localProxyMiddleware struct { // In the future, DBCertChecker is going to be extended so that it's used by both tsh and Connect // and this middleware will be removed. func (m *localProxyMiddleware) OnNewConnection(ctx context.Context, lp *alpn.LocalProxy, conn net.Conn) error { - m.log.Debug("Checking local proxy certs") - err := lp.CheckDBCerts(m.dbRoute) if err == nil { return nil