Add audit event for Duo service account auto-provisioning

What does this MR do and why?

Adds a new DAP-specific audit event, duo_service_account_provisioned, that fires whenever a service account is auto-provisioned via Ai::Catalog::ItemConsumers::CreateService (for AI Catalog flows or third-party flows).

Currently, service account auto-provisioning only emits the generic member_created event, which is indistinguishable from a regular user being added to a project. SIEM rules cannot tell DAP-related service account creation apart from regular membership changes, and there is no clear audit trail of which flow/agent triggered the service account creation.

Changes

  • New audit event type (ee/config/audit_events/types/duo_service_account_provisioned.yml): Defines duo_service_account_provisioned scoped to [Group, Project, User], saved_to_database: true, streamed: true, feature category duo_agent_platform.
  • Audit call in Ai::Catalog::ItemConsumers::CreateService (ee/app/services/ai/catalog/item_consumers/create_service.rb): After a successful service account creation in execute, a new log_service_account_provisioned_audit_event private method emits the event. It includes:
    • service account username, id, composite-identity flag, provisioned-by group
    • associated item (flow/agent) id, type, name
    • foundational flow reference (when applicable)
    • item consumer id
    • the developer access level used when adding the SA to a project
  • Spec (ee/spec/services/ai/catalog/item_consumers/duo_service_account_provisioned_audit_spec.rb): Covers
    • successful flow provisioning at group scope (scope/target/details assertions)
    • foundational flow context surfaces in the message + additional_details
    • service account creation failure does NOT fire the new event
    • agent items (no SA provisioned) do NOT fire the new event
    • nil cases at unit level: nil service account, nil item consumer, both nil, and item without a foundational flow reference

Note on duplicate audit events

Both the generic member_created event and duo_service_account_provisioned will fire when a Duo SA is auto-provisioned. This is intentional: member_created is generic membership, while duo_service_account_provisioned carries DAP-specific scope and context for SIEM filtering. This mirrors the pattern from !229464 (service_account_created).

References

Screenshots

image Screenshot_2026-06-01_at_17.02.26

How to set up and validate locally

  1. Ensure you have an Ultimate license (audit events require it).

  2. Enable a DAP flow (e.g. via the AI Catalog Item Consumer GraphQL mutation aiCatalogItemConsumerCreate) targeting a top-level group, with an item whose type is flow or third_party_flow.

  3. Verify in the Rails console. AuditEvent has no event_name column — the event name lives inside the YAML-serialized details hash, so filter in Ruby:

    # Most recent matching event
    AuditEvent.order(id: :desc).limit(200).find do |e|
      e.details[:event_name] == 'duo_service_account_provisioned'
    end
    
    # Or scope by the provisioned service account (faster, indexed)
    sa = User.find_by(username: '<service-account-username>')
    AuditEvent.where(target_id: sa.id, target_type: 'User')
              .order(id: :desc)
              .find { |e| e.details[:event_name] == 'duo_service_account_provisioned' }
  4. Confirm the event has the expected scope (group/project), target (the service account), and additional_details (item id/type/name, foundational flow reference if any).

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.

Edited by SAM FIGUEROA

Merge request reports

Loading