diff --git a/changes/issue-7888-login-logs b/changes/issue-7888-login-logs new file mode 100644 index 0000000000..8fe1be9580 --- /dev/null +++ b/changes/issue-7888-login-logs @@ -0,0 +1 @@ +* Added logging to capture user email upon successful login \ No newline at end of file diff --git a/server/contexts/logging/logging.go b/server/contexts/logging/logging.go index 1816a2c12c..3a3abdf60c 100644 --- a/server/contexts/logging/logging.go +++ b/server/contexts/logging/logging.go @@ -132,7 +132,6 @@ func (l *LoggingContext) Log(ctx context.Context, logger kitlog.Logger) { } keyvals = append(keyvals, "user", loggedInUser) } - requestMethod, ok := ctx.Value(kithttp.ContextKeyRequestMethod).(string) if !ok { requestMethod = "" diff --git a/server/service/integration_logger_test.go b/server/service/integration_logger_test.go index ae17eb2ee5..1213a7a134 100644 --- a/server/service/integration_logger_test.go +++ b/server/service/integration_logger_test.go @@ -86,7 +86,7 @@ func (s *integrationLoggerTestSuite) TestLogger() { assert.Equal(t, "GET", kv["method"]) assert.Equal(t, "/api/latest/fleet/config", kv["uri"]) assert.Equal(t, "admin1@example.com", kv["user"]) - case 2: + case 3: assert.Equal(t, "debug", kv["level"]) assert.Equal(t, "POST", kv["method"]) assert.Equal(t, "/api/latest/fleet/queries", kv["uri"]) @@ -99,6 +99,61 @@ func (s *integrationLoggerTestSuite) TestLogger() { } } +func (s *integrationLoggerTestSuite) TestLoggerLogin() { + t := s.T() + + type logEntry struct { + key string + val string + } + + testCases := []struct { + loginRequest loginRequest + expectedStatus int + expectedLogs []logEntry + }{ + { + loginRequest: loginRequest{Email: testUsers["admin1"].Email, Password: testUsers["admin1"].PlaintextPassword}, + expectedStatus: http.StatusOK, + expectedLogs: []logEntry{{"email", testUsers["admin1"].Email}}, + }, + { + loginRequest: loginRequest{Email: testUsers["admin1"].Email, Password: "n074v411dp455w02d"}, + expectedStatus: http.StatusUnauthorized, + expectedLogs: []logEntry{ + {"email", testUsers["admin1"].Email}, + {"level", "error"}, + {"internal", "invalid password"}, + }, + }, + { + loginRequest: loginRequest{Email: "h4x0r@3x4mp13.c0m", Password: "n074v411dp455w02d"}, + expectedStatus: http.StatusUnauthorized, + expectedLogs: []logEntry{ + {"email", "h4x0r@3x4mp13.c0m"}, + {"level", "error"}, + {"internal", "user not found"}, + }, + }, + } + var resp loginResponse + for _, tt := range testCases { + s.DoJSON("POST", "/api/latest/fleet/login", tt.loginRequest, tt.expectedStatus, &resp) + logString := s.buf.String() + parts := strings.Split(strings.TrimSpace(logString), "\n") + require.Len(t, parts, 1) + logData := make(map[string]string) + require.NoError(t, json.Unmarshal([]byte(parts[0]), &logData)) + + require.NotContains(t, logData, "user") // logger context is set to skip user + + for _, e := range tt.expectedLogs { + assert.Equal(t, logData[e.key], e.val) + } + s.buf.Reset() + } +} + func (s *integrationLoggerTestSuite) TestOsqueryEndpointsLogErrors() { t := s.T() diff --git a/server/service/sessions.go b/server/service/sessions.go index 5086cf6318..d9b009dd69 100644 --- a/server/service/sessions.go +++ b/server/service/sessions.go @@ -153,7 +153,7 @@ 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.WithNoUser(ctx), level.Info) + logging.WithLevel(logging.WithExtras(logging.WithNoUser(ctx), "email", email), 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 diff --git a/server/service/sessions_test.go b/server/service/sessions_test.go index 36305f2b4b..9a2c8d3473 100644 --- a/server/service/sessions_test.go +++ b/server/service/sessions_test.go @@ -98,7 +98,7 @@ func TestAuthenticate(t *testing.T) { svc := newTestService(t, ds, nil, nil) createTestUsers(t, ds) - var loginTests = []struct { + loginTests := []struct { name string email string password string