Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 66 additions & 9 deletions lib/teleterm/vnet/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,17 @@ func (p *clientApplication) GetDialOptions(ctx context.Context, profileName stri
return dialOpts, nil
}

// OnNewSSHSession submits a usage event for a new SSH session.
func (p *clientApplication) OnNewSSHSession(ctx context.Context, profileName, targetClusterName string) {
// Enqueue the event from a separate goroutine since we don't care about errors anyway and we also
// don't want to slow down VNet connections.
go func() {
if err := p.usageReporter.ReportSSHSession(ctx, profileName, targetClusterName); err != nil {
log.ErrorContext(ctx, "Failed to submit SSH usage event")
}
}()
}

// OnNewConnection submits a usage event once per clientApplication lifetime.
// That is, if a user makes multiple connections to a single app, OnNewConnection submits a single
// event. This is to mimic how Connect submits events for its app gateways. This lets us compare
Expand All @@ -542,9 +553,8 @@ func (p *clientApplication) OnNewConnection(ctx context.Context, appKey *vnetv1.

// Not passing ctx to ReportApp since ctx is tied to the lifetime of the connection.
// If it's a short-lived connection, inheriting its context would interrupt reporting.
Comment thread
nklaassen marked this conversation as resolved.
err := p.usageReporter.ReportApp(uri)
if err != nil {
log.ErrorContext(ctx, "Failed to submit usage event", "app", uri, "error", err)
if err := p.usageReporter.ReportApp(uri); err != nil {
log.ErrorContext(ctx, "Failed to submit app usage event", "app", uri, "error", err)
}
}()

Expand Down Expand Up @@ -606,6 +616,7 @@ func (p *clientApplication) OnInvalidLocalPort(ctx context.Context, appInfo *vne

type usageReporter interface {
ReportApp(uri.ResourceURI) error
ReportSSHSession(ctx context.Context, profileName, targetClusterName string) error
Stop()
}

Expand Down Expand Up @@ -675,6 +686,48 @@ func newDaemonUsageReporter(cfg daemonUsageReporterConfig) (*daemonUsageReporter
}, nil
}

// ReportSSHSession adds an event for a new SSH session to the events queue.
Comment thread
nklaassen marked this conversation as resolved.
func (r *daemonUsageReporter) ReportSSHSession(ctx context.Context, profileName, targetClusterName string) error {
r.mu.Lock()
defer r.mu.Unlock()

if r.closed.Load() {
return trace.CompareFailed("usage reported has been stopped")
Comment thread
nklaassen marked this conversation as resolved.
Outdated
}

rootClusterURI := uri.NewClusterURI(profileName)
_, tc, err := r.cfg.ClientCache.ResolveClusterURI(rootClusterURI)
if err != nil {
return trace.Wrap(err)
}

clusterID, ok := r.cfg.ClusterIDCache.Load(rootClusterURI)
if !ok {
return trace.NotFound("cluster ID for %q not found", rootClusterURI)
}

log.DebugContext(ctx, "Reporting SSH usage event", "profile", profileName, "cluster", targetClusterName)
if err := r.cfg.EventConsumer.ReportUsageEvent(&apiteleterm.ReportUsageEventRequest{
AuthClusterId: clusterID,
PrehogReq: &prehogv1alpha.SubmitConnectEventRequest{
DistinctId: r.cfg.InstallationID,
Timestamp: timestamppb.Now(),
Event: &prehogv1alpha.SubmitConnectEventRequest_ProtocolUse{
ProtocolUse: &prehogv1alpha.ConnectProtocolUseEvent{
ClusterName: targetClusterName,
Comment thread
nklaassen marked this conversation as resolved.
Outdated
UserName: tc.Username,
Protocol: "ssh",
Origin: "vnet",
AccessThrough: "vnet",
},
},
},
}); err != nil {
return trace.Wrap(err, "adding SSH usage event to queue")
}
return nil
}

// ReportApp adds an event related to the given app to the events queue, if the app wasn't reported
// already. Only one invocation of ReportApp can be in flight at a time.
func (r *daemonUsageReporter) ReportApp(appURI uri.ResourceURI) error {
Expand Down Expand Up @@ -716,9 +769,9 @@ func (r *daemonUsageReporter) ReportApp(appURI uri.ResourceURI) error {
return trace.NotFound("cluster ID for %q not found", rootClusterURI)
}

log.DebugContext(ctx, "Reporting usage event", "app", appURI.String())
log.DebugContext(ctx, "Reporting app usage event", "app", appURI.String())

err = r.cfg.EventConsumer.ReportUsageEvent(&apiteleterm.ReportUsageEventRequest{
if err := r.cfg.EventConsumer.ReportUsageEvent(&apiteleterm.ReportUsageEventRequest{
AuthClusterId: clusterID,
PrehogReq: &prehogv1alpha.SubmitConnectEventRequest{
DistinctId: r.cfg.InstallationID,
Expand All @@ -733,9 +786,8 @@ func (r *daemonUsageReporter) ReportApp(appURI uri.ResourceURI) error {
},
},
},
})
if err != nil {
return trace.Wrap(err, "adding usage event to queue")
}); err != nil {
return trace.Wrap(err, "adding app usage event to queue")
}

r.reportedApps[appURI.String()] = struct{}{}
Expand All @@ -762,7 +814,12 @@ func (r *daemonUsageReporter) Stop() {
type disabledTelemetryUsageReporter struct{}

func (r *disabledTelemetryUsageReporter) ReportApp(appURI uri.ResourceURI) error {
log.DebugContext(context.Background(), "Skipping usage event, usage reporting is turned off", "app", appURI.String())
log.DebugContext(context.Background(), "Skipping app usage event, usage reporting is turned off", "app", appURI.String())
return nil
}

func (r *disabledTelemetryUsageReporter) ReportSSHSession(ctx context.Context, profileName, targetClusterName string) error {
log.DebugContext(ctx, "Skipping SSH usage event, usage reporting is turned off", "profile", profileName, "cluster", targetClusterName)
return nil
}

Expand Down
5 changes: 5 additions & 0 deletions lib/teleterm/vnet/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ func TestDaemonUsageReporter(t *testing.T) {
err = usageReporter.ReportApp(clusterWithoutClusterID.AppendApp("bar"))
require.ErrorIs(t, err, trace.NotFound("cluster ID for \"/clusters/no-cluster-id\" not found"))
require.Equal(t, 1, eventConsumer.EventCount())

// Verify that reporting an SSH session works.
err = usageReporter.ReportSSHSession(context.Background(), validCluster.GetProfileName(), "foo")
require.NoError(t, err)
require.Equal(t, 2, eventConsumer.EventCount())
}

func TestDaemonUsageReporter_Stop(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions lib/vnet/client_application_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,10 @@ func (s *clientApplicationService) SessionSSHConfig(ctx context.Context, req *vn
return nil, trace.Errorf("user KeyRing host no trusted SSH CAs for cluster %s", targetCluster)
}
sessionID := s.setSignerForSSHSession(keyRing.SSHPrivateKey)

// Submit usage event.
s.cfg.clientApplication.OnNewSSHSession(ctx, req.GetProfile(), targetCluster)

return &vnetv1.SessionSSHConfigResponse{
SessionId: sessionID,
Cert: sshCert.Marshal(),
Expand Down
4 changes: 4 additions & 0 deletions lib/vnet/user_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ type ClientApplication interface {
// GetDialOptions returns ALPN dial options for the profile.
GetDialOptions(ctx context.Context, profileName string) (*vnetv1.DialOptions, error)

// OnNewSSHSession should be called whenever a new SSH session is about to be
// started, after getting the user SSH certificate for the session.
OnNewSSHSession(ctx context.Context, profileName, targetClusterName string)

// OnNewConnection gets called whenever a new connection is about to be established through VNet.
// By the time OnNewConnection, VNet has already verified that the user holds a valid cert for the
// app.
Expand Down
5 changes: 5 additions & 0 deletions tool/tsh/common/vnet_client_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ func (p *vnetClientApplication) GetDialOptions(ctx context.Context, profileName
return dialOpts, nil
}

// OnNewSSHSession gets called before each VNet SSH connection. It's a noop as
// tsh doesn't need to do anything extra here.
func (p *vnetClientApplication) OnNewSSHSession(ctx context.Context, profileName, targetClusterName string) {
}

// OnNewConnection gets called before each VNet connection. It's a noop as tsh doesn't need to do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, OnNewConnection ceased to be a good name and the comment makes it sound like it's being called for each new connection.

This name is used in many places so renaming it might be a bit of a pain, if you don't feel like taking care of this right now, then could you update the comments at least?

$ rg -B 2 "func.*OnNewConnection" -g 'lib/vnet/**' -g 'lib/teleterm/vnet/**' -g 'tool/tsh/**'
tool/tsh/common/vnet_client_application.go
142-// OnNewConnection gets called before each VNet connection. It's a noop as tsh doesn't need to do
143-// anything extra here.
144:func (p *vnetClientApplication) OnNewConnection(_ context.Context, _ *vnetv1.AppKey) error {

lib/vnet/client_application_service_client.go
136-
137-// OnNewConnection reports a new TCP connection to the target app.
138:func (c *clientApplicationServiceClient) OnNewConnection(ctx context.Context, appKey *vnetv1.AppKey) error {

lib/vnet/client_application_service.go
220-// OnNewConnection gets called whenever a new connection is about to be
221-// established through VNet for observability.
222:func (s *clientApplicationService) OnNewConnection(ctx context.Context, req *vnetv1.OnNewConnectionRequest) (*vnetv1.OnNewConnectionResponse, error) {

lib/vnet/app_provider.go
76-
77-// OnNewConnection reports a new TCP connection to the target app.
78:func (p *appProvider) OnNewConnection(ctx context.Context, appKey *vnetv1.AppKey) error {

lib/vnet/vnet_test.go
571-}
572-
573:func (p *fakeClientApp) OnNewConnection(_ context.Context, _ *vnetv1.AppKey) error {
--
1045-// TestOnNewConnection tests that the client applications OnNewConnection method
1046-// is called when a user connects to a valid TCP app.
1047:func TestOnNewConnection(t *testing.T) {

lib/vnet/app_handler.go
173-}
174-
175:func (m *localProxyMiddleware) OnNewConnection(ctx context.Context, lp *alpnproxy.LocalProxy) error {

lib/teleterm/vnet/service.go
549-// event. This is to mimic how Connect submits events for its app gateways. This lets us compare
550-// popularity of VNet and app gateways.
551:func (p *clientApplication) OnNewConnection(ctx context.Context, appKey *vnetv1.AppKey) error {

// OnNewConnection gets called whenever a new connection is about to be
// established through VNet for observability.
rpc OnNewConnection(OnNewConnectionRequest) returns (OnNewConnectionResponse);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i did the rename

// anything extra here.
func (p *vnetClientApplication) OnNewConnection(_ context.Context, _ *vnetv1.AppKey) error {
Expand Down
Loading