Skip to content

feat: add audit logs for dormancy events #15298

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
fix oidc, add test
  • Loading branch information
coadler committed Oct 31, 2024
commit 6fdb233058523c7cee71e0f96e7477326928e40f
8 changes: 6 additions & 2 deletions coderd/audit/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ const (
BackgroundSubsystemDormancy BackgroundSubsystem = "dormancy"
)

func BackgroundTaskFields(ctx context.Context, logger slog.Logger, subsystem BackgroundSubsystem) []byte {
af := map[string]string{
func BackgroundTaskFields(subsystem BackgroundSubsystem) map[string]string {
return map[string]string{
"automatic_actor": "coder",
"automatic_subsystem": string(subsystem),
}
}

func BackgroundTaskFieldsBytes(ctx context.Context, logger slog.Logger, subsystem BackgroundSubsystem) []byte {
af := BackgroundTaskFields(subsystem)

wriBytes, err := json.Marshal(af)
if err != nil {
Expand Down
22 changes: 14 additions & 8 deletions coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,15 +613,19 @@ func ActivateDormantUser(logger slog.Logger, auditor *atomic.Pointer[audit.Audit
return user, xerrors.Errorf("update user status: %w", err)
}

oldAuditUser := user
newAuditUser := user
newAuditUser.Status = database.UserStatusActive

audit.BackgroundAudit(ctx, &audit.BackgroundAuditParams[database.User]{
Audit: *auditor.Load(),
Log: logger,
UserID: user.ID,
Action: database.AuditActionWrite,
Old: user,
New: newUser,
Old: oldAuditUser,
New: newAuditUser,
Status: http.StatusOK,
AdditionalFields: audit.BackgroundTaskFields(ctx, logger, audit.BackgroundSubsystemDormancy),
AdditionalFields: audit.BackgroundTaskFieldsBytes(ctx, logger, audit.BackgroundSubsystemDormancy),
})

return newUser, nil
Expand Down Expand Up @@ -1420,11 +1424,12 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
dormantConvertAudit *audit.Request[database.User]
initDormantAuditOnce = sync.OnceFunc(func() {
dormantConvertAudit = params.initAuditRequest(&audit.RequestParams{
Audit: auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionWrite,
OrganizationID: uuid.Nil,
Audit: auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionWrite,
OrganizationID: uuid.Nil,
AdditionalFields: audit.BackgroundTaskFields(audit.BackgroundSubsystemDormancy),
})
})
)
Expand Down Expand Up @@ -1543,6 +1548,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
// This is necessary because transactions can be retried, and we
// only want to add the audit log a single time.
initDormantAuditOnce()
dormantConvertAudit.UserID = user.ID
dormantConvertAudit.Old = user
//nolint:gocritic // System needs to update status of the user account (dormant -> active).
user, err = tx.UpdateUserStatus(dbauthz.AsSystemRestricted(ctx), database.UpdateUserStatusParams{
Expand Down
45 changes: 44 additions & 1 deletion coderd/userauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@ func TestUserOIDC(t *testing.T) {
tc.AssertResponse(t, resp)
}

ctx := testutil.Context(t, testutil.WaitLong)
ctx := testutil.Context(t, testutil.WaitShort)

if tc.AssertUser != nil {
user, err := client.User(ctx, "me")
Expand All @@ -1300,6 +1300,49 @@ func TestUserOIDC(t *testing.T) {
})
}

t.Run("OIDCDormancy", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitShort)

auditor := audit.NewMock()
fake := oidctest.NewFakeIDP(t,
oidctest.WithRefresh(func(_ string) error {
return xerrors.New("refreshing token should never occur")
}),
oidctest.WithServing(),
)
cfg := fake.OIDCConfig(t, nil, func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
})

logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
owner, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
Auditor: auditor,
OIDCConfig: cfg,
Logger: &logger,
})

user := dbgen.User(t, db, database.User{
LoginType: database.LoginTypeOIDC,
Status: database.UserStatusDormant,
})
auditor.ResetLogs()

client, resp := fake.AttemptLogin(t, owner, jwt.MapClaims{
"email": user.Email,
})
require.Equal(t, http.StatusOK, resp.StatusCode)

auditor.Contains(t, database.AuditLog{
ResourceType: database.ResourceTypeUser,
AdditionalFields: json.RawMessage(`{"automatic_actor":"coder","automatic_subsystem":"dormancy"}`),
})
me, err := client.User(ctx, "me")
require.NoError(t, err)

require.Equal(t, codersdk.UserStatusActive, me.Status)
})

t.Run("OIDCConvert", func(t *testing.T) {
t.Parallel()

Expand Down
2 changes: 1 addition & 1 deletion enterprise/coderd/dormancy/dormantusersjob.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func CheckInactiveUsersWithOptions(ctx context.Context, logger slog.Logger, clk
Old: database.User{ID: u.ID, Username: u.Username, Status: database.UserStatusActive},
New: database.User{ID: u.ID, Username: u.Username, Status: database.UserStatusDormant},
Status: http.StatusOK,
AdditionalFields: audit.BackgroundTaskFields(ctx, logger, audit.BackgroundSubsystemDormancy),
AdditionalFields: audit.BackgroundTaskFieldsBytes(ctx, logger, audit.BackgroundSubsystemDormancy),
})
}
logger.Debug(ctx, "checking user accounts is done", slog.F("num_dormant_accounts", len(updatedUsers)), slog.F("execution_time", time.Since(startTime)))
Expand Down
Loading