Skip to content

Commit

Permalink
emit app.session.start when creating an app session
Browse files Browse the repository at this point in the history
  • Loading branch information
rudream committed Jul 25, 2024
1 parent d7043e4 commit a624a5f
Show file tree
Hide file tree
Showing 16 changed files with 2,104 additions and 1,794 deletions.
2,070 changes: 1,141 additions & 929 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 @@ -336,6 +336,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 @@ -832,6 +834,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 @@ -4605,6 +4605,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,674 changes: 860 additions & 814 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 @@ -2042,6 +2042,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 @@ -3116,6 +3118,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 @@ -3202,6 +3202,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 @@ -3285,6 +3287,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 @@ -1769,6 +1769,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 @@ -31,10 +31,12 @@ 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/entitlements"
"github.com/gravitational/teleport/lib/cryptosuites"
"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 @@ -307,6 +309,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 @@ -365,6 +375,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 @@ -478,6 +490,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 @@ -897,6 +897,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.
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,
})
assert.NoError(t, err)

for _, e := range es {
if e.GetCode() == events.AppSessionStartCode {
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

0 comments on commit a624a5f

Please sign in to comment.