diff --git a/api/types/wrappers/wrappers.go b/api/types/wrappers/wrappers.go index 5f11b3e4151a4..7de3c632fc8d9 100644 --- a/api/types/wrappers/wrappers.go +++ b/api/types/wrappers/wrappers.go @@ -66,6 +66,9 @@ func (l Traits) Clone() Traits { } clone := make(Traits, len(l)) for key, vals := range l { + if len(vals) == 0 || len(vals) == 1 && vals[0] == "" { + continue + } clone[key] = slices.Clone(vals) } return clone diff --git a/lib/bpf/bpf.go b/lib/bpf/bpf.go index e37933b4bcc51..b8eed16561176 100644 --- a/lib/bpf/bpf.go +++ b/lib/bpf/bpf.go @@ -28,6 +28,7 @@ import ( "embed" "encoding/binary" "net" + "slices" "strconv" "sync" "time" @@ -413,6 +414,8 @@ func (s *Service) emitCommandEvent(eventBytes []byte) { User: ctx.User, Login: ctx.Login, UserClusterName: ctx.UserOriginClusterName, + UserRoles: slices.Clone(ctx.UserRoles), + UserTraits: ctx.UserTraits.Clone(), }, BPFMetadata: apievents.BPFMetadata{ CgroupID: event.CgroupID, @@ -473,6 +476,8 @@ func (s *Service) emitDiskEvent(eventBytes []byte) { User: ctx.User, Login: ctx.Login, UserClusterName: ctx.UserOriginClusterName, + UserRoles: slices.Clone(ctx.UserRoles), + UserTraits: ctx.UserTraits.Clone(), }, BPFMetadata: apievents.BPFMetadata{ CgroupID: event.CgroupID, @@ -529,6 +534,8 @@ func (s *Service) emit4NetworkEvent(eventBytes []byte) { User: ctx.User, Login: ctx.Login, UserClusterName: ctx.UserOriginClusterName, + UserRoles: slices.Clone(ctx.UserRoles), + UserTraits: ctx.UserTraits.Clone(), }, BPFMetadata: apievents.BPFMetadata{ CgroupID: event.CgroupID, @@ -587,6 +594,8 @@ func (s *Service) emit6NetworkEvent(eventBytes []byte) { User: ctx.User, Login: ctx.Login, UserClusterName: ctx.UserOriginClusterName, + UserRoles: slices.Clone(ctx.UserRoles), + UserTraits: ctx.UserTraits.Clone(), }, BPFMetadata: apievents.BPFMetadata{ CgroupID: event.CgroupID, diff --git a/lib/bpf/common.go b/lib/bpf/common.go index 1534d7d2405d9..485f48c641916 100644 --- a/lib/bpf/common.go +++ b/lib/bpf/common.go @@ -25,6 +25,7 @@ import ( "github.com/gravitational/trace" apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/lib/utils" ) @@ -84,6 +85,11 @@ type SessionContext struct { // Events is the set of events (command, disk, or network) to record for // this session. Events map[string]bool + + // UserRoles are the roles assigned to the user. + UserRoles []string + // UserTraits are the traits assigned to the user. + UserTraits wrappers.Traits } // NOP is used on either non-Linux systems or when BPF support is not enabled. diff --git a/lib/srv/authhandlers.go b/lib/srv/authhandlers.go index 9b8b16670b1dc..1d6cad7bf645c 100644 --- a/lib/srv/authhandlers.go +++ b/lib/srv/authhandlers.go @@ -257,6 +257,8 @@ func (h *AuthHandlers) CreateIdentityContext(sconn *ssh.ServerConn) (IdentityCon JoinToken: unmappedIdentity.JoinToken, PreviousIdentityExpires: unmappedIdentity.PreviousIdentityExpires, OriginClusterName: certAuthority.GetClusterName(), + MappedRoles: accessInfo.Roles, + Traits: accessInfo.Traits, }, nil } diff --git a/lib/srv/ctx.go b/lib/srv/ctx.go index e74c605be0159..ccf04f1be3b38 100644 --- a/lib/srv/ctx.go +++ b/lib/srv/ctx.go @@ -25,6 +25,7 @@ import ( "log/slog" "net" "os" + "slices" "strconv" "strings" "sync" @@ -42,6 +43,7 @@ import ( tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/moderation" "github.com/gravitational/teleport/lib/bpf" @@ -241,6 +243,13 @@ type IdentityContext struct { // trusted-cluster-related role mapping being applied. UnmappedRoles []string + // MappedRoles lists the final roles of this Teleport user after + // trusted-cluster-related role mapping has been applied. + MappedRoles []string + + // Traits are the identity traits derived from the certificate. + Traits wrappers.Traits + // CertValidBefore is set to the expiry time of a certificate, or // empty, if cert does not expire CertValidBefore time.Time @@ -1112,6 +1121,8 @@ func (id *IdentityContext) GetUserMetadata() apievents.UserMetadata { BotName: id.BotName, BotInstanceID: id.BotInstanceID, UserClusterName: id.OriginClusterName, + UserRoles: slices.Clone(id.MappedRoles), + UserTraits: id.Traits.Clone(), } } diff --git a/lib/srv/ctx_test.go b/lib/srv/ctx_test.go index 1a010155cbc25..3f8655dca42da 100644 --- a/lib/srv/ctx_test.go +++ b/lib/srv/ctx_test.go @@ -31,6 +31,7 @@ import ( decisionpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/decision/v1alpha1" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/lib/services" rsession "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/sshca" @@ -148,6 +149,11 @@ func TestIdentityContext_GetUserMetadata(t *testing.T) { Impersonator: "llama", Login: "alpaca1", ActiveRequests: []string{"access-req1", "access-req2"}, + MappedRoles: []string{"role1", "role2"}, + Traits: wrappers.Traits{ + "trait1": []string{"value1", "value2"}, + "trait2": []string{"value3"}, + }, }, want: apievents.UserMetadata{ User: "alpaca", @@ -155,6 +161,11 @@ func TestIdentityContext_GetUserMetadata(t *testing.T) { Impersonator: "llama", AccessRequests: []string{"access-req1", "access-req2"}, UserKind: apievents.UserKind_USER_KIND_HUMAN, + UserRoles: []string{"role1", "role2"}, + UserTraits: wrappers.Traits{ + "trait1": []string{"value1", "value2"}, + "trait2": []string{"value3"}, + }, }, }, { diff --git a/lib/srv/sess.go b/lib/srv/sess.go index a80e163e27d30..7910cf2b0f44b 100644 --- a/lib/srv/sess.go +++ b/lib/srv/sess.go @@ -1413,6 +1413,8 @@ func (s *session) startInteractive(ctx context.Context, scx *ServerContext, p *p Login: scx.Identity.Login, User: scx.Identity.TeleportUser, UserOriginClusterName: scx.Identity.OriginClusterName, + UserRoles: scx.Identity.MappedRoles, + UserTraits: scx.Identity.Traits, Events: eventsMap, } diff --git a/lib/srv/session_control.go b/lib/srv/session_control.go index 6e923e11978e8..afc2099764d50 100644 --- a/lib/srv/session_control.go +++ b/lib/srv/session_control.go @@ -223,6 +223,8 @@ func WebSessionController(controller *SessionController) func(ctx context.Contex Impersonator: unmappedIdentity.Impersonator, // Web sessions are always local to the cluster they authenticated to. OriginClusterName: clusterName.GetClusterName(), + MappedRoles: accessChecker.RoleNames(), + Traits: accessChecker.Traits(), } ctx, err = controller.AcquireSessionContext(ctx, identity, localAddr, remoteAddr) return ctx, trace.Wrap(err)