diff --git a/changes/9075-log-login b/changes/9075-log-login new file mode 100644 index 000000000..570f1c165 --- /dev/null +++ b/changes/9075-log-login @@ -0,0 +1 @@ +- Log all successful logins as activity and all attempts with ip in stderr. \ No newline at end of file diff --git a/server/fleet/activities.go b/server/fleet/activities.go index 27c5cd927..0ccd5746a 100644 --- a/server/fleet/activities.go +++ b/server/fleet/activities.go @@ -5,6 +5,8 @@ import ( ) const ( + // ActivityTypeUserLoggedIn is the activity type for logging in a user + ActivityTypeUserLoggedIn = "user_logged_in" // ActivityTypeCreatedPack is the activity type for created packs ActivityTypeCreatedPack = "created_pack" // ActivityTypeEditedPack is the activity type for edited packs diff --git a/server/service/http_auth_test.go b/server/service/http_auth_test.go index 320faf3f5..2c89281f7 100644 --- a/server/service/http_auth_test.go +++ b/server/service/http_auth_test.go @@ -172,6 +172,9 @@ func setupAuthTest(t *testing.T) (fleet.Datastore, map[string]fleet.User, *httpt sessions[sessionKey] = session return session, nil } + ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activityType string, details *map[string]interface{}) error { + return nil + } return ds, usersMap, server } diff --git a/server/service/http_publicip.go b/server/service/http_publicip.go index bba6eaa44..ba8b289f7 100644 --- a/server/service/http_publicip.go +++ b/server/service/http_publicip.go @@ -12,7 +12,10 @@ var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") var xRealIP = http.CanonicalHeaderKey("X-Real-IP") func extractIP(r *http.Request) string { - var ip string + ip := r.RemoteAddr + if i := strings.LastIndexByte(ip, ':'); i != -1 { + ip = ip[:i] + } if tcip := r.Header.Get(trueClientIP); tcip != "" { ip = tcip diff --git a/server/service/integration_core_test.go b/server/service/integration_core_test.go index 6e444cbe7..cd19bce7f 100644 --- a/server/service/integration_core_test.go +++ b/server/service/integration_core_test.go @@ -4466,7 +4466,7 @@ func (s *integrationTestSuite) TestAppConfig() { // corresponding activity should not have been created. var listActivities listActivitiesResponse s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusOK, &listActivities, "order_key", "id", "order_direction", "desc") - if !assert.Len(t, listActivities.Activities, 0) { + if !assert.Len(t, listActivities.Activities, 1) { // if there is an activity, make sure it is not edited_agent_options require.NotEqual(t, fleet.ActivityTypeEditedAgentOptions, listActivities.Activities[0].Type) } @@ -4487,7 +4487,7 @@ func (s *integrationTestSuite) TestAppConfig() { "agent_options": { "config": {"views": {"foo": "bar"}} } }`), http.StatusOK, &acResp) s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusOK, &listActivities, "order_key", "id", "order_direction", "desc") - require.True(t, len(listActivities.Activities) > 0) + require.True(t, len(listActivities.Activities) > 1) require.Equal(t, fleet.ActivityTypeEditedAgentOptions, listActivities.Activities[0].Type) require.NotNil(t, listActivities.Activities[0].Details) assert.JSONEq(t, `{"global": true, "team_id": null, "team_name": null}`, string(*listActivities.Activities[0].Details)) diff --git a/server/service/sessions.go b/server/service/sessions.go index e4c252305..4aba5d1f9 100644 --- a/server/service/sessions.go +++ b/server/service/sessions.go @@ -16,6 +16,7 @@ import ( "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/contexts/logging" + "github.com/fleetdm/fleet/v4/server/contexts/publicip" "github.com/fleetdm/fleet/v4/server/contexts/viewer" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/sso" @@ -153,7 +154,11 @@ func (svc *Service) Login(ctx context.Context, email, password string) (*fleet.U // skipauth: No user context available yet to authorize against. svc.authz.SkipAuthorization(ctx) - logging.WithLevel(logging.WithExtras(logging.WithNoUser(ctx), "email", email), level.Info) + logging.WithLevel(logging.WithExtras(logging.WithNoUser(ctx), + "op", "login", + "email", email, + "public_ip", publicip.FromContext(ctx), + ), level.Info) // If there is an error, sleep until the request has taken at least 1 // second. This means that generally a login failure for any reason will @@ -187,6 +192,11 @@ func (svc *Service) Login(ctx context.Context, email, password string) (*fleet.U return nil, nil, fleet.NewAuthFailedError(err.Error()) } + if err := svc.ds.NewActivity(ctx, user, fleet.ActivityTypeUserLoggedIn, &map[string]interface{}{ + "public_ip": publicip.FromContext(ctx), + }); err != nil { + return nil, nil, err + } return user, session, nil }