Add audit event when a user is blocked by LDAP sync
Summary
When LDAP user sync determines a user should be blocked (because they no longer exist in LDAP or are disabled in Active Directory), no audit event is recorded. Customers need user_blocked events traceable to LDAP sync in their audit logs.
This issue is scoped specifically to LDAP user sync blocking. The parent issue #3592 covers broader LDAP audit event gaps including group sync changes.
Related: #3592
Problem
The LDAP blocking logic lives in lib/gitlab/auth/ldap/access.rb in the block_user method. It calls user.ldap_block and logs to Gitlab::AppLogger, but does not create an audit event. This means:
- Admins cannot see in the Audit Log when or why a user was blocked by LDAP sync
- There is no audit trail distinguishing LDAP-initiated blocks from admin-initiated blocks
- Customers relying on audit events for compliance have a gap in their records
The existing user_blocked audit event (defined in ee/config/audit_events/types/user_blocked.yml) is only triggered via Users::BlockService (EE override in ee/app/services/ee/users/block_service.rb), which is used for admin-initiated blocks. The LDAP sync path bypasses this service entirely.
Blocking scenarios to audit
The block_user method in lib/gitlab/auth/ldap/access.rb is called in three cases within the allowed? method:
- User no longer exists in LDAP - reason:
"does not exist anymore" - User is disabled in Active Directory - reason:
"is disabled in Active Directory" - User has no LDAP provider (via
Users::RepairLdapBlockedService) - reason:"Account is not provided by LDAP"
Proposed implementation
1. Create a new audit event type definition
Create ee/config/audit_events/types/user_blocked_by_ldap_sync.yml:
---
name: user_blocked_by_ldap_sync
description: A user is blocked by LDAP synchronization
introduced_by_issue: <this issue URL>
introduced_by_mr: <MR URL>
feature_category: system_access
milestone: 'TBD'
saved_to_database: true
streamed: true
scope: [User]2. Add a hook method in CE lib/gitlab/auth/ldap/access.rb
Add a no-op log_audit_event_for_block(user, reason) method in the CE block_user method, called after user.ldap_block:
def block_user(user, reason)
user.ldap_block
# existing logging...
log_audit_event_for_block(user, reason)
end
def log_audit_event_for_block(user, reason)
# no-op in CE, overridden in EE
end3. Override in EE ee/lib/ee/gitlab/auth/ldap/access.rb
Add the audit event call using the established pattern:
override :log_audit_event_for_block
def log_audit_event_for_block(user, reason)
return unless user.ldap_blocked?
message = if provider
"Blocked user (LDAP account \"#{ldap_identity.extern_uid}\" #{reason})"
else
"Blocked user (account is not provided by LDAP)"
end
::Gitlab::Audit::Auditor.audit({
name: 'user_blocked_by_ldap_sync',
message: message,
author: ::Gitlab::Audit::UnauthenticatedAuthor.new(name: '(LDAP Sync)'),
scope: user,
target: user,
target_details: user.username,
additional_details: {
system_event: true,
reason: reason
}
})
endKey design decisions:
- Uses
UnauthenticatedAuthorwith name'(LDAP Sync)'since this is a system process with no authenticated user (same pattern as SCIM uses'(System)'inee/app/services/ee/members/destroy_service.rb) - Uses
system_event: trueinadditional_detailsto mark it as a system-initiated event - Includes the
reasoninadditional_detailsfor programmatic access - Guard
return unless user.ldap_blocked?ensures the audit event is only logged when the state transition actually succeeded (e.g., not on read-only instances) - Creates a new event type
user_blocked_by_ldap_syncrather than reusinguser_blocked, so customers can filter specifically for LDAP-initiated blocks
4. Add specs in ee/spec/lib/gitlab/auth/ldap/access_spec.rb
Test cases needed:
- User blocked because they no longer exist in LDAP → audit event created with correct message
- User blocked because disabled in Active Directory → audit event created with correct message
- User not blocked (still exists and active) → no audit event
- Not licensed (
admin_audit_log: false,audit_events: false,extended_audit_events: false) → no audit event - Read-only instance → no audit event (because
ldap_blockis a no-op, souser.ldap_blocked?returns false)
Files to modify
| File | Change |
|---|---|
ee/config/audit_events/types/user_blocked_by_ldap_sync.yml |
New audit event type definition |
lib/gitlab/auth/ldap/access.rb |
Add log_audit_event_for_block no-op hook in block_user |
ee/lib/ee/gitlab/auth/ldap/access.rb |
Override log_audit_event_for_block with audit event logic |
ee/spec/lib/gitlab/auth/ldap/access_spec.rb |
Add audit event test cases |
spec/lib/gitlab/auth/ldap/access_spec.rb |
Verify no-op hook doesn't break CE behavior |