Add audit event for CI runner tag changes

What does this MR do and why?

Adds a ci_runner_tags_updated audit event, emitted when a runner's tags (tag_list) change through Ci::Runners::UpdateRunnerService.

Runner lifecycle events (register, unregister, create, assign, unassign, bulk-delete) are already audited, but editing a runner was not. Tags are the runner attribute most relevant to CI access control, so a tag that silently appears or changes on a runner left customers with no audit trail.

This matters most for GitLab Duo. Duo Agent Platform flows only run on runners tagged gitlab--duo (Configure runners to execute flows), so that tag decides where Duo flows execute and which runners receive their credentials. Organizations that need a strict audit trail of where AI tools run can now see who added or removed the tag, and when.

Resolves #637852

Changes

  • ee/app/services/ee/ci/runners/update_runner_service.rb (new): EE override of #execute. When the update includes tag_list, it snapshots the tags before the update and, on success, emits the audit event if the sorted tag set changed. The event stores the old and new tag arrays in additional_details. Updates without tag_list go straight to the CE service.
  • app/services/ci/runners/update_runner_service.rb: adds the trailing prepend_mod. The CE service had no EE extension before.
  • ee/app/services/audit_events/runner_audit_event_service.rb: accepts an optional additional_details: keyword that is stored with the event. Existing callers don't pass it and are unchanged.
  • ee/config/audit_events/types/ci_runner_tags_updated.yml (new): event type definition (saved to database, streamed, Instance/Group/Project scope).
  • ee/spec/services/ee/ci/runners/update_runner_service_spec.rb (new): covers a tag change, all tags removed, tags added to an untagged runner, a tag containing a comma, a tag named none, instance and project runners, unchanged tags, a non-tag attribute change, and a failed update.
  • ee/spec/services/audit_events/runner_audit_event_service_spec.rb: covers passing additional_details through to the auditor.
  • doc/user/compliance/audit_event_types.md: regenerated from the YAML.

Implementation notes

  • Tags come from acts-as-taggable, so runner.previous_changes does not track them. The change is detected by comparing sorted tag_list before and after, similar to how ee/app/services/ci/runner_controllers/update_service.rb snapshots old_state.
  • Reuses AuditEvents::RunnerAuditEventService, which resolves the scope (instance for instance runners, otherwise runner.owner), author, target, and message formatting. Old and new tags go into the %{from} / %{to} message placeholders as bracketed lists, for example "Changed instance CI runner tags from [shared] to [docker, shared]". An empty list is shown as [], so it reads differently from a tag named none ([none]).
  • The message is a readable summary, and the UI and CSV export show only that summary. Because a tag can contain a comma, one tag "docker, gitlab--duo" and two tags docker and gitlab--duo both read as [docker, gitlab--duo]. The exact tag arrays are stored in the event details as changes: [{ change: :tag_list, from:, to: }].
  • The change is nested under changes:, as in Groups::GroupLinks::UpdateService. A top-level change key would make Audit::Details build the displayed action from the raw arrays, so the UI and CSV export would show Changed tag list from ["docker"] to [...] instead of the message. A spec checks the displayed action.
  • No explicit license check: Gitlab::Audit::Auditor decides what is persisted or streamed, consistent with the sibling runner audit services.

Out of scope

  • Auditing other mutable runner settings (locked, access_level, run_untagged, maximum_timeout, paused, description). The same pattern applies.

Screenshots or screen recordings

image.png

image.png

How to set up and validate locally

  1. In the GDK, edit a group runner's tags (Group > Build > Runners > Edit).
  2. Go to Group > Secure > Audit events and confirm a "Changed group CI runner tags from ... to ..." entry.
  3. Save the runner again without changing tags and confirm no new entry.
bin/rspec ee/spec/services/ee/ci/runners/update_runner_service_spec.rb ee/spec/services/audit_events/runner_audit_event_service_spec.rb

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

🤖 Generated with Claude Code

Edited by Cleveland Bledsoe Jr

Merge request reports

Loading
Loading