diff --git a/identity/manager.go b/identity/manager.go index acda53aa8334..8837aefd8c14 100644 --- a/identity/manager.go +++ b/identity/manager.go @@ -7,6 +7,10 @@ import ( "context" "reflect" + "go.opentelemetry.io/otel/trace" + + "github.com/ory/kratos/x/events" + "github.com/ory/x/otelx" "github.com/ory/kratos/x" @@ -86,6 +90,7 @@ func (m *Manager) Create(ctx context.Context, i *Identity, opts ...ManagerOption return err } + trace.SpanFromContext(ctx).AddEvent(events.NewIdentityCreated(ctx, i.ID)) return m.r.PrivilegedIdentityPool().CreateIdentity(ctx, i) } @@ -170,6 +175,7 @@ func (m *Manager) UpdateSchemaID(ctx context.Context, id uuid.UUID, schemaID str return err } + trace.SpanFromContext(ctx).AddEvent(events.NewIdentityUpdated(ctx, id)) return m.r.PrivilegedIdentityPool().UpdateIdentity(ctx, original) } @@ -206,6 +212,7 @@ func (m *Manager) UpdateTraits(ctx context.Context, id uuid.UUID, traits Traits, return err } + trace.SpanFromContext(ctx).AddEvent(events.NewIdentityUpdated(ctx, id)) return m.r.PrivilegedIdentityPool().UpdateIdentity(ctx, updated) } diff --git a/selfservice/flow/login/error.go b/selfservice/flow/login/error.go index ebbc7a468634..94311b92a815 100644 --- a/selfservice/flow/login/error.go +++ b/selfservice/flow/login/error.go @@ -85,13 +85,14 @@ func (s *ErrorHandler) WriteFlowError(w http.ResponseWriter, r *http.Request, f WithField("login_flow", f). Info("Encountered self-service login error.") - trace.SpanFromContext(r.Context()).AddEvent(events.NewLoginFailed(r.Context())) - if f == nil { + trace.SpanFromContext(r.Context()).AddEvent(events.NewLoginFailed(r.Context(), "", "", false)) s.forward(w, r, nil, err) return } + trace.SpanFromContext(r.Context()).AddEvent(events.NewLoginFailed(r.Context(), string(f.Type), string(f.RequestedAAL), f.Refresh)) + if expired, inner := s.PrepareReplacementForExpiredFlow(w, r, f, err); inner != nil { s.WriteFlowError(w, r, f, group, inner) return diff --git a/selfservice/flow/login/hook.go b/selfservice/flow/login/hook.go index 18a31b3b2817..4bd82d76a40d 100644 --- a/selfservice/flow/login/hook.go +++ b/selfservice/flow/login/hook.go @@ -171,6 +171,8 @@ func (e *HookExecutor) PostLoginHook(w http.ResponseWriter, r *http.Request, g n Debug("ExecuteLoginPostHook completed successfully.") } + trace.SpanFromContext(r.Context()).AddEvent(events.NewLoginSucceeded(r.Context(), s.ID, i.ID, string(a.Type), string(a.RequestedAAL), a.Refresh, a.Active.String())) + if a.Type == flow.TypeAPI { if err := e.d.SessionPersister().UpsertSession(r.Context(), s); err != nil { return errors.WithStack(err) @@ -181,8 +183,6 @@ func (e *HookExecutor) PostLoginHook(w http.ResponseWriter, r *http.Request, g n WithField("identity_id", i.ID). Info("Identity authenticated successfully and was issued an Ory Kratos Session Token.") - trace.SpanFromContext(r.Context()).AddEvent(events.NewSessionIssued(r.Context(), s.ID, i.ID)) - if handled, err := e.d.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, g); err != nil { return errors.WithStack(err) } else if handled { @@ -209,8 +209,6 @@ func (e *HookExecutor) PostLoginHook(w http.ResponseWriter, r *http.Request, g n WithField("session_id", s.ID). Info("Identity authenticated successfully and was issued an Ory Kratos Session Cookie.") - trace.SpanFromContext(r.Context()).AddEvent(events.NewSessionIssued(r.Context(), s.ID, i.ID)) - if x.IsJSONRequest(r) { // Browser flows rely on cookies. Adding tokens in the mix will confuse consumers. s.Token = "" diff --git a/selfservice/flow/recovery/error.go b/selfservice/flow/recovery/error.go index 35c4a7022b1b..defc22719412 100644 --- a/selfservice/flow/recovery/error.go +++ b/selfservice/flow/recovery/error.go @@ -7,6 +7,10 @@ import ( "net/http" "net/url" + "go.opentelemetry.io/otel/trace" + + "github.com/ory/kratos/x/events" + "github.com/ory/x/sqlxx" "github.com/ory/kratos/ui/node" @@ -67,10 +71,13 @@ func (s *ErrorHandler) WriteFlowError( Info("Encountered self-service recovery error.") if f == nil { + trace.SpanFromContext(r.Context()).AddEvent(events.NewRecoveryFailed(r.Context(), "", "")) s.forward(w, r, nil, err) return } + trace.SpanFromContext(r.Context()).AddEvent(events.NewRecoveryFailed(r.Context(), string(f.Type), f.Active.String())) + if e := new(flow.ExpiredError); errors.As(err, &e) { strategy, err := s.d.RecoveryStrategies(r.Context()).Strategy(f.Active.String()) if err != nil { diff --git a/selfservice/flow/recovery/hook.go b/selfservice/flow/recovery/hook.go index 12cd16be0627..8fe7eae3d1aa 100644 --- a/selfservice/flow/recovery/hook.go +++ b/selfservice/flow/recovery/hook.go @@ -8,6 +8,10 @@ import ( "fmt" "net/http" + "go.opentelemetry.io/otel/trace" + + "github.com/ory/kratos/x/events" + "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/flow" @@ -98,6 +102,8 @@ func (e *HookExecutor) PostRecoveryHook(w http.ResponseWriter, r *http.Request, Debug("ExecutePostRecoveryHook completed successfully.") } + trace.SpanFromContext(r.Context()).AddEvent(events.NewRecoverySucceeded(r.Context(), string(a.Type), a.Active.String())) + e.d.Logger(). WithRequest(r). WithField("identity_id", s.Identity.ID). diff --git a/selfservice/flow/registration/error.go b/selfservice/flow/registration/error.go index 697a77439c2b..a639d2c20c7d 100644 --- a/selfservice/flow/registration/error.go +++ b/selfservice/flow/registration/error.go @@ -91,12 +91,12 @@ func (s *ErrorHandler) WriteFlowError( WithField("registration_flow", f). Info("Encountered self-service flow error.") - trace.SpanFromContext(r.Context()).AddEvent(events.NewRegistrationFailed(r.Context())) - if f == nil { + trace.SpanFromContext(r.Context()).AddEvent(events.NewRegistrationFailed(r.Context(), "", "")) s.forward(w, r, nil, err) return } + trace.SpanFromContext(r.Context()).AddEvent(events.NewRegistrationFailed(r.Context(), string(f.Type), f.Active.String())) if expired, inner := s.PrepareReplacementForExpiredFlow(w, r, f, err); inner != nil { s.forward(w, r, f, err) diff --git a/selfservice/flow/registration/hook.go b/selfservice/flow/registration/hook.go index 344c3507545c..ec7b5e0128a5 100644 --- a/selfservice/flow/registration/hook.go +++ b/selfservice/flow/registration/hook.go @@ -172,7 +172,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque WithField("identity_id", i.ID). Info("A new identity has registered using self-service registration.") - trace.SpanFromContext(r.Context()).AddEvent(events.NewIdentityCreated(r.Context(), i.ID)) + trace.SpanFromContext(r.Context()).AddEvent(events.NewRegistrationSucceeded(r.Context(), string(a.Type), a.Active.String())) s := session.NewInactiveSession() s.CompletedLoginForWithProvider(ct, identity.AuthenticatorAssuranceLevel1, provider) diff --git a/selfservice/flow/settings/error.go b/selfservice/flow/settings/error.go index 86b0c4c1fa45..8c83be9df9a1 100644 --- a/selfservice/flow/settings/error.go +++ b/selfservice/flow/settings/error.go @@ -8,6 +8,10 @@ import ( "net/http" "net/url" + "go.opentelemetry.io/otel/trace" + + "github.com/ory/kratos/x/events" + "github.com/ory/kratos/session" "github.com/ory/kratos/x/swagger" @@ -167,9 +171,11 @@ func (s *ErrorHandler) WriteFlowError( } if f == nil { - s.forward(w, r, f, err) + trace.SpanFromContext(r.Context()).AddEvent(events.NewSettingsFailed(r.Context(), "", "")) + s.forward(w, r, nil, err) return } + trace.SpanFromContext(r.Context()).AddEvent(events.NewSettingsFailed(r.Context(), string(f.Type), f.Active.String())) if expired, inner := s.PrepareReplacementForExpiredFlow(w, r, f, id, err); inner != nil { s.forward(w, r, f, err) diff --git a/selfservice/flow/settings/hook.go b/selfservice/flow/settings/hook.go index 36be10314da7..4e6aa54ec403 100644 --- a/selfservice/flow/settings/hook.go +++ b/selfservice/flow/settings/hook.go @@ -9,6 +9,10 @@ import ( "net/http" "time" + "go.opentelemetry.io/otel/trace" + + "github.com/ory/kratos/x/events" + "github.com/ory/kratos/session" "github.com/ory/kratos/text" @@ -278,6 +282,8 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request, WithField("flow_method", settingsType). Debug("Completed all PostSettingsPrePersistHooks and PostSettingsPostPersistHooks.") + trace.SpanFromContext(r.Context()).AddEvent(events.NewSettingsSucceeded(r.Context(), string(ctxUpdate.Flow.Type), ctxUpdate.Flow.Active.String())) + if ctxUpdate.Flow.Type == flow.TypeAPI { updatedFlow, err := e.d.SettingsFlowPersister().GetSettingsFlow(r.Context(), ctxUpdate.Flow.ID) if err != nil { diff --git a/selfservice/flow/verification/error.go b/selfservice/flow/verification/error.go index ec33fc2a738f..fd7542415fb1 100644 --- a/selfservice/flow/verification/error.go +++ b/selfservice/flow/verification/error.go @@ -7,6 +7,10 @@ import ( "net/http" "net/url" + "go.opentelemetry.io/otel/trace" + + "github.com/ory/kratos/x/events" + "github.com/ory/kratos/ui/node" "github.com/pkg/errors" @@ -65,9 +69,11 @@ func (s *ErrorHandler) WriteFlowError( Info("Encountered self-service verification error.") if f == nil { + trace.SpanFromContext(r.Context()).AddEvent(events.NewVerificationFailed(r.Context(), "", "")) s.forward(w, r, nil, err) return } + trace.SpanFromContext(r.Context()).AddEvent(events.NewVerificationFailed(r.Context(), string(f.Type), f.Active.String())) if e := new(flow.ExpiredError); errors.As(err, &e) { strategy, err := s.d.VerificationStrategies(r.Context()).Strategy(f.Active.String()) diff --git a/selfservice/flow/verification/hook.go b/selfservice/flow/verification/hook.go index d417e1c02b45..d9e914b79d95 100644 --- a/selfservice/flow/verification/hook.go +++ b/selfservice/flow/verification/hook.go @@ -8,6 +8,10 @@ import ( "fmt" "net/http" + "go.opentelemetry.io/otel/trace" + + "github.com/ory/kratos/x/events" + "github.com/ory/kratos/driver/config" "github.com/ory/kratos/identity" "github.com/ory/kratos/selfservice/flow" @@ -108,6 +112,8 @@ func (e *HookExecutor) PostVerificationHook(w http.ResponseWriter, r *http.Reque Debug("ExecutePostVerificationHook completed successfully.") } + trace.SpanFromContext(r.Context()).AddEvent(events.NewVerificationSucceeded(r.Context(), string(a.Type), a.Active.String())) + e.d.Logger(). WithRequest(r). WithField("identity_id", i.ID). diff --git a/selfservice/hook/session_issuer.go b/selfservice/hook/session_issuer.go index 693c74bd07d8..804e7977bf3e 100644 --- a/selfservice/hook/session_issuer.go +++ b/selfservice/hook/session_issuer.go @@ -8,13 +8,9 @@ import ( "net/http" "time" - "go.opentelemetry.io/otel/trace" - "github.com/ory/kratos/identity" "github.com/ory/kratos/ui/node" - "github.com/ory/kratos/x/events" - "github.com/pkg/errors" "github.com/ory/kratos/driver/config" @@ -62,8 +58,6 @@ func (e *SessionIssuer) executePostRegistrationPostPersistHook(w http.ResponseWr return err } - trace.SpanFromContext(r.Context()).AddEvent(events.NewSessionIssued(r.Context(), s.ID, s.IdentityID)) - if a.Type == flow.TypeAPI { if s.AuthenticatedVia(identity.CredentialsTypeOIDC) { if handled, err := e.r.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, node.OpenIDConnectGroup); err != nil { diff --git a/session/manager_http.go b/session/manager_http.go index fdd42977cbd6..582cffc3e7af 100644 --- a/session/manager_http.go +++ b/session/manager_http.go @@ -9,6 +9,10 @@ import ( "net/url" "time" + "go.opentelemetry.io/otel/trace" + + "github.com/ory/kratos/x/events" + "github.com/ory/kratos/selfservice/flow" "github.com/ory/kratos/selfservice/sessiontokenexchange" "github.com/ory/kratos/ui/node" @@ -65,6 +69,7 @@ func (s *ManagerHTTP) UpsertAndIssueCookie(ctx context.Context, w http.ResponseW ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.UpsertAndIssueCookie") defer otelx.End(span, &err) + isNew := ss.ID == uuid.Nil if err := s.r.SessionPersister().UpsertSession(ctx, ss); err != nil { return err } @@ -73,6 +78,12 @@ func (s *ManagerHTTP) UpsertAndIssueCookie(ctx context.Context, w http.ResponseW return err } + var event = events.NewSessionChanged + if isNew { + event = events.NewSessionIssued + } + + trace.SpanFromContext(r.Context()).AddEvent(event(r.Context(), string(ss.AuthenticatorAssuranceLevel), ss.ID, ss.IdentityID)) return nil } diff --git a/x/events/events.go b/x/events/events.go index ac64d5454b8b..15d9f872d7c6 100644 --- a/x/events/events.go +++ b/x/events/events.go @@ -14,39 +14,167 @@ import ( ) const ( - SessionIssued semconv.Event = "SessionIssued" - SessionRevoked semconv.Event = "SessionRevoked" - RegistrationFailed semconv.Event = "RegistrationFailed" - IdentityCreated semconv.Event = "IdentityCreated" - LoginFailed semconv.Event = "LoginFailed" + SessionIssued semconv.Event = "SessionIssued" + SessionChanged semconv.Event = "SessionChanged" + SessionRevoked semconv.Event = "SessionRevoked" + RegistrationFailed semconv.Event = "RegistrationFailed" + RegistrationSucceeded semconv.Event = "RegistrationSucceeded" + LoginFailed semconv.Event = "LoginFailed" + LoginSucceeded semconv.Event = "LoginSucceeded" + SettingsFailed semconv.Event = "SettingsFailed" + SettingsSucceeded semconv.Event = "SettingsSucceeded" + RecoveryFailed semconv.Event = "RecoveryFailed" + RecoverySucceeded semconv.Event = "RecoverySucceeded" + VerificationFailed semconv.Event = "VerificationFailed" + VerificationSucceeded semconv.Event = "VerificationSucceeded" + IdentityCreated semconv.Event = "IdentityCreated" + IdentityUpdated semconv.Event = "IdentityUpdated" ) const ( - attributeKeySessionID semconv.AttributeKey = "SessionID" + attributeKeySessionID semconv.AttributeKey = "SessionID" + attributeKeySessionAAL semconv.AttributeKey = "SessionAAL" + attributeKeySelfServiceFlowType semconv.AttributeKey = "SelfServiceFlowType" + attributeKeySelfServiceMethodUsed semconv.AttributeKey = "SelfServiceMethodUsed" + attributeKeyLoginRequestedAAL semconv.AttributeKey = "LoginRequestedAAL" + attributeKeyLoginRequestedPrivilegedSession semconv.AttributeKey = "LoginRequestedPrivilegedSession" ) func attrSessionID(val uuid.UUID) otelattr.KeyValue { return otelattr.String(attributeKeySessionID.String(), val.String()) } -func NewSessionIssued(ctx context.Context, sessionID, identityID uuid.UUID) (string, trace.EventOption) { +func attrSessionAAL(val string) otelattr.KeyValue { + return otelattr.String(attributeKeySessionAAL.String(), val) +} + +func attLoginRequestedAAL(val string) otelattr.KeyValue { + return otelattr.String(attributeKeyLoginRequestedAAL.String(), val) +} + +func attLoginRequestedPrivilegedSession(val bool) otelattr.KeyValue { + return otelattr.Bool(attributeKeyLoginRequestedPrivilegedSession.String(), val) +} + +func attrSelfServiceFlowType(val string) otelattr.KeyValue { + return otelattr.String(attributeKeySelfServiceFlowType.String(), val) +} + +func attrSelfServiceMethodUsed(val string) otelattr.KeyValue { + return otelattr.String(attributeKeySelfServiceMethodUsed.String(), val) +} + +func NewSessionIssued(ctx context.Context, aal string, sessionID, identityID uuid.UUID) (string, trace.EventOption) { return SessionIssued.String(), trace.WithAttributes( append( semconv.AttributesFromContext(ctx), semconv.AttrIdentityID(identityID), attrSessionID(sessionID), + attrSessionAAL(aal), )..., ) } -func NewRegistrationFailed(ctx context.Context) (string, trace.EventOption) { - return RegistrationFailed.String(), +func NewSessionChanged(ctx context.Context, aal string, sessionID, identityID uuid.UUID) (string, trace.EventOption) { + return SessionChanged.String(), trace.WithAttributes( - semconv.AttributesFromContext(ctx)..., + append( + semconv.AttributesFromContext(ctx), + semconv.AttrIdentityID(identityID), + attrSessionID(sessionID), + attrSessionAAL(aal), + )..., + ) +} + +func NewLoginSucceeded(ctx context.Context, sessionID, identityID uuid.UUID, flowType string, requestedAAL string, isRefresh bool, method string) (string, trace.EventOption) { + return LoginSucceeded.String(), + trace.WithAttributes( + append( + semconv.AttributesFromContext(ctx), + semconv.AttrIdentityID(identityID), + attrSessionID(sessionID), + attrSelfServiceFlowType(flowType), + attLoginRequestedAAL(requestedAAL), + attLoginRequestedPrivilegedSession(isRefresh), + attrSelfServiceMethodUsed(method), + )..., ) } +func NewRegistrationSucceeded(ctx context.Context, flowType string, method string) (string, trace.EventOption) { + return RegistrationSucceeded.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceFlowType(flowType), + attrSelfServiceMethodUsed(method), + )...) +} + +func NewRecoverySucceeded(ctx context.Context, flowType string, method string) (string, trace.EventOption) { + return RecoverySucceeded.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceFlowType(flowType), + attrSelfServiceMethodUsed(method), + )...) +} + +func NewSettingsSucceeded(ctx context.Context, flowType string, method string) (string, trace.EventOption) { + return SettingsSucceeded.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceFlowType(flowType), + attrSelfServiceMethodUsed(method), + )...) +} + +func NewVerificationSucceeded(ctx context.Context, flowType string, method string) (string, trace.EventOption) { + return VerificationSucceeded.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceMethodUsed(method), + attrSelfServiceFlowType(flowType), + )...) +} + +func NewRegistrationFailed(ctx context.Context, flowType string, method string) (string, trace.EventOption) { + return RegistrationFailed.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceFlowType(flowType), + attrSelfServiceMethodUsed(method), + )...) +} + +func NewRecoveryFailed(ctx context.Context, flowType string, method string) (string, trace.EventOption) { + return RecoveryFailed.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceFlowType(flowType), + attrSelfServiceMethodUsed(method), + )...) +} + +func NewSettingsFailed(ctx context.Context, flowType string, method string) (string, trace.EventOption) { + return SettingsFailed.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceFlowType(flowType), + attrSelfServiceMethodUsed(method), + )...) +} + +func NewVerificationFailed(ctx context.Context, flowType string, method string) (string, trace.EventOption) { + return VerificationFailed.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceFlowType(flowType), + attrSelfServiceMethodUsed(method), + )...) +} + func NewIdentityCreated(ctx context.Context, identityID uuid.UUID) (string, trace.EventOption) { return IdentityCreated.String(), trace.WithAttributes( @@ -57,13 +185,26 @@ func NewIdentityCreated(ctx context.Context, identityID uuid.UUID) (string, trac ) } -func NewLoginFailed(ctx context.Context) (string, trace.EventOption) { - return LoginFailed.String(), +func NewIdentityUpdated(ctx context.Context, identityID uuid.UUID) (string, trace.EventOption) { + return IdentityUpdated.String(), trace.WithAttributes( - semconv.AttributesFromContext(ctx)..., + append( + semconv.AttributesFromContext(ctx), + semconv.AttrIdentityID(identityID), + )..., ) } +func NewLoginFailed(ctx context.Context, flowType string, requestedAAL string, isRefresh bool) (string, trace.EventOption) { + return LoginFailed.String(), + trace.WithAttributes(append( + semconv.AttributesFromContext(ctx), + attrSelfServiceFlowType(flowType), + attLoginRequestedAAL(requestedAAL), + attLoginRequestedPrivilegedSession(isRefresh), + )...) +} + func NewSessionRevoked(ctx context.Context, sessionID, identityID uuid.UUID) (string, trace.EventOption) { return SessionRevoked.String(), trace.WithAttributes(