diff --git a/appsec/appsec.go b/appsec/appsec.go index 33286cdc17..5d0c1ec8f9 100644 --- a/appsec/appsec.go +++ b/appsec/appsec.go @@ -50,6 +50,10 @@ func MonitorParsedHTTPBody(ctx context.Context, body any) error { // APM tracer middleware on use according to your blocking configuration. // This function always returns nil when appsec is disabled and doesn't block users. func SetUser(ctx context.Context, id string, opts ...tracer.UserMonitoringOption) error { + return setUser(ctx, id, usersec.UserSet, opts) +} + +func setUser(ctx context.Context, id string, userEventType usersec.UserEventType, opts []tracer.UserMonitoringOption) error { s, ok := tracer.SpanFromContext(ctx) if !ok { log.Debug("appsec: could not retrieve span from context. User ID tag won't be set") @@ -61,11 +65,10 @@ func SetUser(ctx context.Context, id string, opts ...tracer.UserMonitoringOption return nil } - op, errPtr := usersec.StartUserLoginOperation(ctx, usersec.UserLoginOperationArgs{}) + op, errPtr := usersec.StartUserLoginOperation(ctx, userEventType, usersec.UserLoginOperationArgs{}) op.Finish(usersec.UserLoginOperationRes{ UserID: id, SessionID: getSessionID(opts...), - Success: true, }) return *errPtr @@ -85,7 +88,7 @@ func SetUser(ctx context.Context, id string, opts ...tracer.UserMonitoringOption // associated to them. func TrackUserLoginSuccessEvent(ctx context.Context, uid string, md map[string]string, opts ...tracer.UserMonitoringOption) error { TrackCustomEvent(ctx, "users.login.success", md) - return SetUser(ctx, uid, opts...) + return setUser(ctx, uid, usersec.UserLoginSuccess, opts) } // TrackUserLoginFailureEvent sets a failed user login event, with the given @@ -111,8 +114,8 @@ func TrackUserLoginFailureEvent(ctx context.Context, uid string, exists bool, md TrackCustomEvent(ctx, "users.login.failure", md) - op, _ := usersec.StartUserLoginOperation(ctx, usersec.UserLoginOperationArgs{}) - op.Finish(usersec.UserLoginOperationRes{UserID: uid, Success: false}) + op, _ := usersec.StartUserLoginOperation(ctx, usersec.UserLoginFailure, usersec.UserLoginOperationArgs{}) + op.Finish(usersec.UserLoginOperationRes{UserID: uid}) } // TrackCustomEvent sets a custom event as service entry span tags. This span is diff --git a/internal/appsec/emitter/usersec/user.go b/internal/appsec/emitter/usersec/user.go index 50a4352e6d..235da876bb 100644 --- a/internal/appsec/emitter/usersec/user.go +++ b/internal/appsec/emitter/usersec/user.go @@ -7,36 +7,58 @@ package usersec import ( "context" + "sync" "gopkg.in/DataDog/dd-trace-go.v1/appsec/events" "gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo" + "gopkg.in/DataDog/dd-trace-go.v1/internal/log" ) const errorLog = ` appsec: user login monitoring ignored: could not find the http handler instrumentation metadata in the request context: - the request handler is not being monitored by a middleware function or the provided context is not the expected request context + the request handler is not being monitored by a middleware function or the provided context is not the expected request context. + If the user has been blocked using remote rules, blocking will still be enforced but it will not be reported. ` +var errorLogOnce sync.Once + type ( + // UserEventType is the type of user event, such as a successful login or a failed login or any other authenticated request. + UserEventType int + // UserLoginOperation type representing a call to appsec.SetUser(). It gets both created and destroyed in a single // call to ExecuteUserIDOperation UserLoginOperation struct { dyngo.Operation + EventType UserEventType } // UserLoginOperationArgs is the user ID operation arguments. - UserLoginOperationArgs struct{} + UserLoginOperationArgs struct { + } // UserLoginOperationRes is the user ID operation results. UserLoginOperationRes struct { UserID string SessionID string - Success bool } ) -func StartUserLoginOperation(ctx context.Context, args UserLoginOperationArgs) (*UserLoginOperation, *error) { - parent, _ := dyngo.FromContext(ctx) - op := &UserLoginOperation{Operation: dyngo.NewOperation(parent)} +const ( + // UserLoginSuccess is the event type for a successful user login, when a new session or JWT is created. + UserLoginSuccess UserEventType = iota + // UserLoginFailure is the event type for a failed user login, when the user ID is not found or the password is incorrect. + UserLoginFailure + // UserSet is the event type for a user ID operation that is not a login, such as any authenticated request made by the user. + UserSet +) + +func StartUserLoginOperation(ctx context.Context, eventType UserEventType, args UserLoginOperationArgs) (*UserLoginOperation, *error) { + parent, ok := dyngo.FromContext(ctx) + if !ok { // Nothing will be reported in this case, but we can still block so we don't return + errorLogOnce.Do(func() { log.Error(errorLog) }) + } + + op := &UserLoginOperation{Operation: dyngo.NewOperation(parent), EventType: eventType} var err error dyngo.OnData(op, func(e *events.BlockingSecurityEvent) { err = e }) dyngo.StartOperation(op, args) diff --git a/internal/appsec/listener/usersec/usec.go b/internal/appsec/listener/usersec/usec.go index c8a6458019..5989604578 100644 --- a/internal/appsec/listener/usersec/usec.go +++ b/internal/appsec/listener/usersec/usec.go @@ -37,14 +37,19 @@ func NewUserSecFeature(cfg *config.Config, rootOp dyngo.Operation) (listener.Fea } func (*Feature) OnFinish(op *usersec.UserLoginOperation, res usersec.UserLoginOperationRes) { - builder := addresses.NewAddressesBuilder(). - WithUserID(res.UserID). - WithUserSessionID(res.SessionID) - - if res.Success { - builder = builder.WithUserLoginSuccess() - } else { - builder = builder.WithUserLoginFailure() + builder := addresses.NewAddressesBuilder() + + switch op.EventType { + case usersec.UserLoginSuccess: + builder = builder.WithUserLoginSuccess(). + WithUserID(res.UserID). + WithUserSessionID(res.SessionID) + case usersec.UserLoginFailure: + builder = builder.WithUserLoginFailure(). + WithUserID(res.UserID) + case usersec.UserSet: + builder = builder.WithUserID(res.UserID). + WithUserSessionID(res.SessionID) } dyngo.EmitData(op, waf.RunEvent{