Log all successful logins as activity and all attempts with ip in stderr. (#9095)

This commit is contained in:
Frank Sievertsen 2022-12-21 18:29:51 +01:00 committed by GitHub
parent 98c2ef98f7
commit e7d6ed0f3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 23 additions and 4 deletions

1
changes/9075-log-login Normal file
View File

@ -0,0 +1 @@
- Log all successful logins as activity and all attempts with ip in stderr.

View File

@ -5,6 +5,8 @@ import (
) )
const ( const (
// ActivityTypeUserLoggedIn is the activity type for logging in a user
ActivityTypeUserLoggedIn = "user_logged_in"
// ActivityTypeCreatedPack is the activity type for created packs // ActivityTypeCreatedPack is the activity type for created packs
ActivityTypeCreatedPack = "created_pack" ActivityTypeCreatedPack = "created_pack"
// ActivityTypeEditedPack is the activity type for edited packs // ActivityTypeEditedPack is the activity type for edited packs

View File

@ -172,6 +172,9 @@ func setupAuthTest(t *testing.T) (fleet.Datastore, map[string]fleet.User, *httpt
sessions[sessionKey] = session sessions[sessionKey] = session
return session, nil 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 return ds, usersMap, server
} }

View File

@ -12,7 +12,10 @@ var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
var xRealIP = http.CanonicalHeaderKey("X-Real-IP") var xRealIP = http.CanonicalHeaderKey("X-Real-IP")
func extractIP(r *http.Request) string { 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 != "" { if tcip := r.Header.Get(trueClientIP); tcip != "" {
ip = tcip ip = tcip

View File

@ -4466,7 +4466,7 @@ func (s *integrationTestSuite) TestAppConfig() {
// corresponding activity should not have been created. // corresponding activity should not have been created.
var listActivities listActivitiesResponse var listActivities listActivitiesResponse
s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusOK, &listActivities, "order_key", "id", "order_direction", "desc") 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 // if there is an activity, make sure it is not edited_agent_options
require.NotEqual(t, fleet.ActivityTypeEditedAgentOptions, listActivities.Activities[0].Type) require.NotEqual(t, fleet.ActivityTypeEditedAgentOptions, listActivities.Activities[0].Type)
} }
@ -4487,7 +4487,7 @@ func (s *integrationTestSuite) TestAppConfig() {
"agent_options": { "config": {"views": {"foo": "bar"}} } "agent_options": { "config": {"views": {"foo": "bar"}} }
}`), http.StatusOK, &acResp) }`), http.StatusOK, &acResp)
s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusOK, &listActivities, "order_key", "id", "order_direction", "desc") 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.Equal(t, fleet.ActivityTypeEditedAgentOptions, listActivities.Activities[0].Type)
require.NotNil(t, listActivities.Activities[0].Details) require.NotNil(t, listActivities.Activities[0].Details)
assert.JSONEq(t, `{"global": true, "team_id": null, "team_name": null}`, string(*listActivities.Activities[0].Details)) assert.JSONEq(t, `{"global": true, "team_id": null, "team_name": null}`, string(*listActivities.Activities[0].Details))

View File

@ -16,6 +16,7 @@ import (
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/logging" "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/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/sso" "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. // skipauth: No user context available yet to authorize against.
svc.authz.SkipAuthorization(ctx) 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 // 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 // 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()) 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 return user, session, nil
} }