Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v16] Emit app.session.start event on tsh app login and tsh proxy app #44792

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2,048 changes: 1,130 additions & 918 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions api/proto/teleport/legacy/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ message RouteToApp {
string AzureIdentity = 6 [(gogoproto.jsontag) = "azure_identity,omitempty"];
// GCPServiceAccount is the GCP service account to assume when accessing GCP API.
string GCPServiceAccount = 7 [(gogoproto.jsontag) = "gcp_service_account,omitempty"];
// URI is the URI of the app. This is the internal endpoint where the application is running and isn't user-facing.
string URI = 8 [(gogoproto.jsontag) = "uri,omitempty"];
}

// GetUserRequest specifies parameters for the GetUser method.
Expand Down Expand Up @@ -788,6 +790,12 @@ message CreateAppSessionRequest {
// An optional field, that when provided, the response will be validated and
// the ID of the validated MFA device will be stored in session's certificate.
MFAAuthenticateResponse MFAResponse = 8 [(gogoproto.jsontag) = "mfa_response,omitempty"];
// AppName is the name of the application.
string AppName = 9 [(gogoproto.jsontag) = "app_name"];
// URI is the URI of the app. This is the internal endpoint where the application is running and isn't user-facing.
string URI = 10 [(gogoproto.jsontag) = "uri"];
// ClientAddr is a client (user's) address.
string ClientAddr = 11 [(gogoproto.jsontag) = "client_addr,omitempty"];
}

// CreateAppSessionResponse contains the requested application web session.
Expand Down
2 changes: 2 additions & 0 deletions api/proto/teleport/legacy/types/events/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4592,6 +4592,8 @@ message RouteToApp {
string AzureIdentity = 6 [(gogoproto.jsontag) = "azure_identity,omitempty"];
// GCPServiceAccount is the GCP service account to assume when accessing GCP API.
string GCPServiceAccount = 7 [(gogoproto.jsontag) = "gcp_service_account,omitempty"];
// URI is the application URI.
string URI = 8 [(gogoproto.jsontag) = "uri,omitempty"];
}

// RouteToDatabase combines parameters for database service routing information.
Expand Down
1,901 changes: 973 additions & 928 deletions api/types/events/events.pb.go

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3562,6 +3562,8 @@ message RouteToApp {
string AzureIdentity = 6 [(gogoproto.jsontag) = "azure_identity,omitempty"];
// GCPServiceAccount is the GCP service account to assume when accessing GCP API.
string GCPServiceAccount = 7 [(gogoproto.jsontag) = "gcp_service_account,omitempty"];
// URI is the application URI.
string URI = 8 [(gogoproto.jsontag) = "uri,omitempty"];
}

// RouteToDatabase combines parameters for database service routing information.
Expand Down
3 changes: 3 additions & 0 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,8 @@ type certRequest struct {
appClusterName string
// appName is the name of the application to generate cert for.
appName string
// appURI is the URI of the app. This is the internal endpoint where the application is running and isn't user-facing.
appURI string
// awsRoleARN is the role ARN to generate certificate for.
awsRoleARN string
// azureIdentity is the Azure identity to generate certificate for.
Expand Down Expand Up @@ -3027,6 +3029,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types.
KubernetesUsers: kubeUsers,
RouteToApp: tlsca.RouteToApp{
SessionID: req.appSessionID,
URI: req.appURI,
PublicAddr: req.appPublicAddr,
ClusterName: req.appClusterName,
Name: req.appName,
Expand Down
3 changes: 3 additions & 0 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -3185,6 +3185,8 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
GCPServiceAccount: req.RouteToApp.GCPServiceAccount,
MFAVerified: verifiedMFADeviceID,
DeviceExtensions: DeviceExtensions(a.context.Identity.GetIdentity().DeviceExtensions),
AppName: req.RouteToApp.Name,
AppURI: req.RouteToApp.URI,
})
if err != nil {
return nil, trace.Wrap(err)
Expand Down Expand Up @@ -3236,6 +3238,7 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
appSessionID: appSessionID,
appName: req.RouteToApp.Name,
appPublicAddr: req.RouteToApp.PublicAddr,
appURI: req.RouteToApp.URI,
appClusterName: req.RouteToApp.ClusterName,
awsRoleARN: req.RouteToApp.AWSRoleARN,
azureIdentity: req.RouteToApp.AzureIdentity,
Expand Down
2 changes: 2 additions & 0 deletions lib/auth/authclient/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -1730,6 +1730,8 @@ func TryCreateAppSessionForClientCertV15(ctx context.Context, client CreateAppSe
AWSRoleARN: routeToApp.AWSRoleARN,
AzureIdentity: routeToApp.AzureIdentity,
GCPServiceAccount: routeToApp.GCPServiceAccount,
URI: routeToApp.URI,
AppName: routeToApp.Name,
})
if err != nil {
return "", trace.Wrap(err)
Expand Down
48 changes: 48 additions & 0 deletions lib/auth/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ import (
apidefaults "github.com/gravitational/teleport/api/defaults"
mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/auth/native"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/jwt"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/services"
Expand Down Expand Up @@ -267,6 +269,14 @@ type NewAppSessionRequest struct {
MFAVerified string
// DeviceExtensions holds device-aware user certificate extensions.
DeviceExtensions DeviceExtensions
// AppName is the name of the app.
AppName string
// AppURI is the URI of the app. This is the internal endpoint where the application is running and isn't user-facing.
AppURI string
// Identity is the identity of the user.
Identity tlsca.Identity
// ClientAddr is a client (user's) address.
ClientAddr string
}

// CreateAppSession creates and inserts a services.WebSession into the
Expand Down Expand Up @@ -325,6 +335,8 @@ func (a *Server) CreateAppSession(ctx context.Context, req *proto.CreateAppSessi
AzureIdentity: req.AzureIdentity,
GCPServiceAccount: req.GCPServiceAccount,
MFAVerified: verifiedMFADeviceID,
AppName: req.AppName,
AppURI: req.URI,
DeviceExtensions: DeviceExtensions(identity.DeviceExtensions),
})
if err != nil {
Expand Down Expand Up @@ -430,6 +442,42 @@ func (a *Server) CreateAppSessionFromReq(ctx context.Context, req NewAppSessionR
}
log.Debugf("Generated application web session for %v with TTL %v.", req.User, req.SessionTTL)
UserLoginCount.Inc()

userMetadata := req.Identity.GetUserMetadata()
userMetadata.User = session.GetUser()
userMetadata.AWSRoleARN = req.AWSRoleARN

err = a.emitter.EmitAuditEvent(a.closeCtx, &apievents.AppSessionStart{
Metadata: apievents.Metadata{
Type: events.AppSessionStartEvent,
Code: events.AppSessionStartCode,
ClusterName: req.ClusterName,
},
ServerMetadata: apievents.ServerMetadata{
ServerVersion: teleport.Version,
ServerID: a.ServerID,
ServerNamespace: apidefaults.Namespace,
},
SessionMetadata: apievents.SessionMetadata{
SessionID: session.GetName(),
WithMFA: req.MFAVerified,
PrivateKeyPolicy: string(req.Identity.PrivateKeyPolicy),
},
UserMetadata: userMetadata,
ConnectionMetadata: apievents.ConnectionMetadata{
RemoteAddr: req.ClientAddr,
},
PublicAddr: req.PublicAddr,
AppMetadata: apievents.AppMetadata{
AppURI: req.AppURI,
AppPublicAddr: req.PublicAddr,
AppName: req.AppName,
},
})
if err != nil {
log.WithError(err).Warn("Failed to emit app session start event")
}

return session, nil
}

Expand Down
1 change: 1 addition & 0 deletions lib/teleterm/clusters/cluster_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func (c *Cluster) ReissueAppCert(ctx context.Context, clusterClient *client.Clus
AWSRoleARN: "",
AzureIdentity: "",
GCPServiceAccount: "",
URI: app.GetURI(),
}

// TODO (Joerger): DELETE IN v17.0.0
Expand Down
4 changes: 4 additions & 0 deletions lib/tlsca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ type RouteToApp struct {

// GCPServiceAccount is the GCP service account to assume when accessing GCP API.
GCPServiceAccount string

// URI is the URI of the app. This is the internal endpoint where the application is running and isn't user-facing.
URI string
}

// RouteToDatabase contains routing information for databases.
Expand Down Expand Up @@ -304,6 +307,7 @@ func (id *Identity) GetEventIdentity() events.Identity {
AWSRoleARN: id.RouteToApp.AWSRoleARN,
AzureIdentity: id.RouteToApp.AzureIdentity,
GCPServiceAccount: id.RouteToApp.GCPServiceAccount,
URI: id.RouteToApp.URI,
}
}
var routeToDatabase *events.RouteToDatabase
Expand Down
54 changes: 3 additions & 51 deletions lib/web/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,13 @@ import (
"github.com/gravitational/trace"
"github.com/julienschmidt/httprouter"

"github.com/gravitational/teleport"
apiclient "github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/client/proto"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/httplib"
"github.com/gravitational/teleport/lib/reversetunnelclient"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/web/app"
"github.com/gravitational/teleport/lib/web/ui"
Expand Down Expand Up @@ -260,58 +256,14 @@ func (h *Handler) createAppSession(w http.ResponseWriter, r *http.Request, p htt
ClusterName: result.ClusterName,
AWSRoleARN: req.AWSRole,
MFAResponse: mfaProtoResponse,
AppName: result.App.GetName(),
URI: result.App.GetURI(),
ClientAddr: r.RemoteAddr,
})
if err != nil {
return nil, trace.Wrap(err)
}

// Extract the identity of the user.
certificate, err := tlsca.ParseCertificatePEM(ws.GetTLSCert())
if err != nil {
return nil, trace.Wrap(err)
}
identity, err := tlsca.FromSubject(certificate.Subject, certificate.NotAfter)
if err != nil {
return nil, trace.Wrap(err)
}

userMetadata := identity.GetUserMetadata()
userMetadata.User = ws.GetUser()
userMetadata.AWSRoleARN = req.AWSRole

// Now that the certificate has been issued, emit a "new session created"
// for all events associated with this certificate.
appSessionStartEvent := &apievents.AppSessionStart{
Metadata: apievents.Metadata{
Type: events.AppSessionStartEvent,
Code: events.AppSessionStartCode,
ClusterName: identity.RouteToApp.ClusterName,
},
ServerMetadata: apievents.ServerMetadata{
ServerVersion: teleport.Version,
ServerID: h.cfg.HostUUID,
ServerNamespace: apidefaults.Namespace,
},
SessionMetadata: apievents.SessionMetadata{
SessionID: identity.RouteToApp.SessionID,
WithMFA: identity.MFAVerified,
PrivateKeyPolicy: string(identity.PrivateKeyPolicy),
},
UserMetadata: userMetadata,
ConnectionMetadata: apievents.ConnectionMetadata{
RemoteAddr: r.RemoteAddr,
},
PublicAddr: identity.RouteToApp.PublicAddr,
AppMetadata: apievents.AppMetadata{
AppURI: result.App.GetURI(),
AppPublicAddr: result.App.GetPublicAddr(),
AppName: result.App.GetName(),
},
}
if err := h.cfg.Emitter.EmitAuditEvent(h.cfg.Context, appSessionStartEvent); err != nil {
return nil, trace.Wrap(err)
}

return &CreateAppSessionResponse{
CookieValue: ws.GetName(),
SubjectCookieValue: ws.GetBearerToken(),
Expand Down
1 change: 1 addition & 0 deletions tool/tctl/common/auth_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,7 @@ func (a *AuthCommand) generateUserKeys(ctx context.Context, clusterAPI certifica
Name: a.appName,
PublicAddr: server.GetApp().GetPublicAddr(),
ClusterName: a.leafCluster,
URI: server.GetApp().GetURI(),
}

// TODO (Joerger): DELETE IN v17.0.0
Expand Down
3 changes: 3 additions & 0 deletions tool/tsh/common/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ func onAppConfig(cf *CLIConf) error {
AWSRoleARN: app.AWSRoleARN,
AzureIdentity: app.AzureIdentity,
GCPServiceAccount: app.GCPServiceAccount,
URI: app.GetURI(),
}
conf, err := formatAppConfig(tc, profile, routeToApp, cf.Format)
if err != nil {
Expand Down Expand Up @@ -528,6 +529,7 @@ func getAppInfo(cf *CLIConf, tc *client.TeleportClient, matchRouteToApp func(tls
Name: app.GetName(),
PublicAddr: app.GetPublicAddr(),
ClusterName: tc.SiteName,
URI: app.GetURI(),
},
app: app,
}
Expand Down Expand Up @@ -655,5 +657,6 @@ func tlscaRouteToAppToProto(route tlsca.RouteToApp) proto.RouteToApp {
AWSRoleARN: route.AWSRoleARN,
AzureIdentity: route.AzureIdentity,
GCPServiceAccount: route.GCPServiceAccount,
URI: route.URI,
}
}
22 changes: 22 additions & 0 deletions tool/tsh/common/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ import (
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib"
"github.com/gravitational/teleport/lib/asciitable"
"github.com/gravitational/teleport/lib/auth/mocku2f"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/service/servicecfg"
testserver "github.com/gravitational/teleport/tool/teleport/testenv"
)
Expand Down Expand Up @@ -261,6 +263,26 @@ func TestAppCommands(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, app.name, resp.Header.Get("Server"))

// Verify that the app.session.start event was emitted.
if app.cluster == "root" {
require.EventuallyWithT(t, func(t *assert.CollectT) {
now := time.Now()
ctx := context.Background()
es, _, err := rootAuthServer.SearchEvents(ctx, events.SearchEventsRequest{
From: now.Add(-time.Hour),
To: now.Add(time.Hour),
Order: types.EventOrderDescending,
EventTypes: []string{events.AppSessionStartEvent},
})
assert.NoError(t, err)

for _, e := range es {
assert.Equal(t, e.(*apievents.AppSessionStart).AppName, app.name)
return
}
t.Errorf("failed to find AppSessionStartCode event (0/%d events matched)", len(es))
}, 5*time.Second, 500*time.Millisecond)
}
// app logout.
err = Run(ctx, []string{
"app",
Expand Down
1 change: 1 addition & 0 deletions tool/tsh/common/vnet_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (p *vnetAppProvider) reissueAppCert(ctx context.Context, tc *client.Telepor
Name: app.GetName(),
PublicAddr: app.GetPublicAddr(),
ClusterName: tc.SiteName,
URI: app.GetURI(),
}

profile, err := tc.ProfileStatus()
Expand Down
Loading