Attribute system hook audit events to organizations

What does this MR do and why?

System hook audit events were attributed to the acting user: EE::WebHooks::CreateService and EE::WebHooks::DestroyService fell back to scope: current_user whenever a hook had no parent, so a SystemHook created or deleted on one organization landed in that user's audit log and could not be filtered per organization. Any updates to webhooks were not audited at all because there was no WebHooks::UpdateService; the controller and API wrote to the model directly.

This MR:

  • Scopes SystemHook create and destroy audit events to Gitlab::Audit::InstanceScope (matching AdminDestroyService) and records organization_id in additional_details, via a new shared EE::WebHooks::AuditHelpers module used by all three EE services. ProjectHook, GroupHook and ServiceHook behaviour is unchanged.
  • Adds a CE WebHooks::UpdateService with an EE override that emits a new webhook_updated audit event. The event lists only the names of changed attributes (encrypted columns mapped back to token, url, url_variables, custom_headers; *_iv and updated_at dropped) so secrets and URL credentials never reach the audit log. No event is emitted on a no-op save.
  • Routes every hook mutation path through the service: WebHooks::HookActions#update, PUT /hooks/:id (API::Helpers::WebHooksHelpers#update_hook), and the URL variable and custom header PUT/DELETE endpoints. Since those endpoints are mounted for project, group and system hooks, update auditing covers all three.
  • Extracts the shared validation error mapping into WebHooks::ValidationErrors so API response bodies are unchanged.
  • Adds config/audit_events/types/webhook_updated.yml and regenerates doc/user/compliance/audit_event_types.md.
  • Adds an entry to .labkit_logging_todo.yml for the organization_id detail now flattened into Gitlab::AuditJsonLogger lines.

Why not a first-class Organization audit scope?

There is no Organization audit scope today: Gitlab::Audit::ScopeValidation, AuditEvents::BuildService, type_schema.json and the per-scope partitioned tables only know Project, Group, User and Instance. Introducing one is a cross-team change owned by group::compliance and is already raised as an open question in #607246. This MR therefore attributes the event to the organization through organization_id on an instance-scoped event, which is also what EE::Organizations::SoftDeleteService / HardDeleteService do. I suggest amending the issue wording accordingly.

References

Screenshots or screen recordings

No UI changes. Sample webhook_updated event details for a system hook:

{
  "organization_id": 1,
  "changed_attributes": ["url", "push_events", "custom_headers"]
}

How to set up and validate locally

  1. Ensure an Ultimate licence is active (audit events are a licensed feature).
  2. Create a system hook as an admin and change it:
    admin = User.admins.first
    hook  = WebHooks::CreateService.new(admin).execute({ url: 'https://example.com/a' }, SystemHook.new)[:hook]
    WebHooks::UpdateService.new(admin).execute(hook, { url: 'https://example.com/b', token: 'secret' })
  3. Inspect the events:
    AuditEvents::InstanceAuditEvent.where(target_type: 'SystemHook').order(:id).pluck(:event_name, :details)
    Expect webhook_created and webhook_updated, both with organization_id, and changed_attributes containing url and token but no values.
  4. Confirm no AuditEvents::UserAuditEvent was created for the admin.
  5. Repeat via the API to cover the other paths:
    curl -X PUT --header "PRIVATE-TOKEN: $TOKEN" \
      "http://gdk.test:3000/api/v4/hooks/$HOOK_ID/url_variables/foo" --data "value=bar"
  6. Create and update a project hook through the UI and confirm a webhook_updated event scoped to the project appears under Settings > Audit events.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #632601 (closed)

Edited by Carla Drago

Merge request reports

Loading
Loading