diff --git a/doc/administration/audit_event_types.md b/doc/administration/audit_event_types.md
index 8bc33d680aec66171fb765d18f4d804c2cb34898..2a33cc2afff610699b8cdf94dfa88b0e4f5d137b 100644
--- a/doc/administration/audit_event_types.md
+++ b/doc/administration/audit_event_types.md
@@ -58,11 +58,12 @@ Audit event types belong to the following product categories.
| [`audit_events_streaming_instance_headers_destroy`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127228) | Triggered when a streaming header for instance level external audit event destination is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/417433) | Instance |
| [`audit_events_streaming_instance_headers_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127228) | Triggered when a streaming header for instance level external audit event destination is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/417433) | Instance |
| [`create_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74632) | Event triggered when an external audit event destination is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/344664) | Group |
-| [`create_group_audit_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147888) | Event triggered when an external audit event destination for a top-level group is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/436610) | Group |
| [`create_http_namespace_filter`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136047) | Event triggered when a namespace filter for an external audit event destination for a top-level group is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.6](https://gitlab.com/gitlab-org/gitlab/-/issues/424176) | Group |
| [`create_instance_audit_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148383) | Event triggered when an external audit event destination for a GitLab instance is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/436615) | Instance |
| [`create_instance_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123882) | Event triggered when an instance level external audit event destination is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.2](https://gitlab.com/gitlab-org/gitlab/-/issues/404730) | Instance |
+| [`created_group_audit_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147888) | Event triggered when an external audit event destination for a top-level group is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/436610) | Group |
| [`delete_http_namespace_filter`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136302) | Event triggered when a namespace filter for an external audit event destination for a top-level group is deleted.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/424177) | Group |
+| [`deleted_group_audit_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148738) | Event triggered when an external audit event destination for a top-level group is deleted.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/436610) | Group |
| [`destroy_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74632) | Event triggered when an external audit event destination is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/344664) | Group |
| [`destroy_instance_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125846) | Event triggered when an instance level external audit event destination is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.2](https://gitlab.com/gitlab-org/gitlab/-/issues/404730) | Instance |
| [`event_type_filters_created`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113081) | Event triggered when a new audit events streaming event type filter is created| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.10](https://gitlab.com/gitlab-org/gitlab/-/issues/344848) | Group |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index f8f58e443f283f6bcbbdf00ff3ebfde4e267318b..2e3e2d2511ca932eaac9d58a30cb47dfedf1cd51 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -4747,6 +4747,28 @@ Input type: `GroupAuditEventStreamingDestinationsCreateInput`
| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| `externalAuditEventDestination` | [`GroupAuditEventStreamingDestination`](#groupauditeventstreamingdestination) | Destination created. |
+### `Mutation.groupAuditEventStreamingDestinationsDelete`
+
+DETAILS:
+**Introduced** in GitLab 16.11.
+**Status**: Experiment.
+
+Input type: `GroupAuditEventStreamingDestinationsDeleteInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `id` | [`AuditEventsGroupExternalStreamingDestinationID!`](#auditeventsgroupexternalstreamingdestinationid) | ID of the audit events external streaming destination to delete. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+
### `Mutation.groupMemberBulkUpdate`
Input type: `GroupMemberBulkUpdateInput`
@@ -33943,6 +33965,12 @@ A `AuditEventsGoogleCloudLoggingConfigurationID` is a global ID. It is encoded a
An example `AuditEventsGoogleCloudLoggingConfigurationID` is: `"gid://gitlab/AuditEvents::GoogleCloudLoggingConfiguration/1"`.
+### `AuditEventsGroupExternalStreamingDestinationID`
+
+A `AuditEventsGroupExternalStreamingDestinationID` is a global ID. It is encoded as a string.
+
+An example `AuditEventsGroupExternalStreamingDestinationID` is: `"gid://gitlab/AuditEvents::Group::ExternalStreamingDestination/1"`.
+
### `AuditEventsInstanceAmazonS3ConfigurationID`
A `AuditEventsInstanceAmazonS3ConfigurationID` is a global ID. It is encoded as a string.
diff --git a/ee/app/graphql/ee/types/mutation_type.rb b/ee/app/graphql/ee/types/mutation_type.rb
index 389a8cd7972d9ef4619ce822830a4b174f27e860..7bcaa2270944110abb3540f1efddabf074d66e29 100644
--- a/ee/app/graphql/ee/types/mutation_type.rb
+++ b/ee/app/graphql/ee/types/mutation_type.rb
@@ -167,6 +167,8 @@ module MutationType
mount_mutation ::Mutations::ApprovalProjectRules::Delete, alpha: { milestone: '16.10' }
mount_mutation ::Mutations::AuditEvents::Group::AuditEventStreamingDestinations::Create,
alpha: { milestone: '16.11' }
+ mount_mutation ::Mutations::AuditEvents::Group::AuditEventStreamingDestinations::Delete,
+ alpha: { milestone: '16.11' }
mount_mutation ::Mutations::AuditEvents::Instance::AuditEventStreamingDestinations::Create,
alpha: { milestone: '16.11' }
diff --git a/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/base.rb b/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/base.rb
index 05593b46d6590ef02ea84675cda3971e7a98208e..ca0f9aafb67641f28987161f8624b001e1d9e327 100644
--- a/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/base.rb
+++ b/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/base.rb
@@ -5,6 +5,8 @@ module AuditEvents
module Group
module AuditEventStreamingDestinations
class Base < BaseMutation
+ authorize :admin_external_audit_events
+
private
def audit(destination, action:)
diff --git a/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/create.rb b/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/create.rb
index 06aecb419d41a0e6671a88abda0c8e5a9ca5ea27..ccf8e7ed25de4d81ec36d541c846adccfbd2923b 100644
--- a/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/create.rb
+++ b/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/create.rb
@@ -7,8 +7,6 @@ module AuditEventStreamingDestinations
class Create < Base
graphql_name 'GroupAuditEventStreamingDestinationsCreate'
- authorize :admin_external_audit_events
-
argument :config, GraphQL::Types::JSON, # rubocop:disable Graphql/JSONType -- Different type of destinations will have different configs
required: true,
description: 'Destination config.'
@@ -42,7 +40,7 @@ def resolve(group_path:, secret_token: nil, name: nil, category: nil, config: ni
category: category
)
- audit(destination, action: :create) if destination.save
+ audit(destination, action: :created) if destination.save
{
external_audit_event_destination: (destination if destination.persisted?),
diff --git a/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/delete.rb b/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/delete.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2a9a9d36d1efaaadb4096b21d27fbc65b541adb8
--- /dev/null
+++ b/ee/app/graphql/mutations/audit_events/group/audit_event_streaming_destinations/delete.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AuditEvents
+ module Group
+ module AuditEventStreamingDestinations
+ class Delete < Base
+ graphql_name 'GroupAuditEventStreamingDestinationsDelete'
+
+ argument :id, ::Types::GlobalIDType[::AuditEvents::Group::ExternalStreamingDestination],
+ required: true,
+ description: 'ID of the audit events external streaming destination to delete.'
+
+ def resolve(id:)
+ config = authorized_find!(id: id)
+
+ audit(config, action: :deleted) if config.destroy
+ { errors: Array(config.errors) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/ee/config/audit_events/types/create_group_audit_event_streaming_destination.yml b/ee/config/audit_events/types/created_group_audit_event_streaming_destination.yml
similarity index 86%
rename from ee/config/audit_events/types/create_group_audit_event_streaming_destination.yml
rename to ee/config/audit_events/types/created_group_audit_event_streaming_destination.yml
index c8e283594ddac7bb4fa9010d441dd3e984ddda68..fe9ee454eb03caf626df40933bac92309c76c3b0 100644
--- a/ee/config/audit_events/types/create_group_audit_event_streaming_destination.yml
+++ b/ee/config/audit_events/types/created_group_audit_event_streaming_destination.yml
@@ -1,4 +1,4 @@
-name: create_group_audit_event_streaming_destination
+name: created_group_audit_event_streaming_destination
description: Event triggered when an external audit event destination for a top-level group is created.
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/436610
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147888
diff --git a/ee/config/audit_events/types/deleted_group_audit_event_streaming_destination.yml b/ee/config/audit_events/types/deleted_group_audit_event_streaming_destination.yml
new file mode 100644
index 0000000000000000000000000000000000000000..de1fa52eefec7d55241443a8a9440160735af4f3
--- /dev/null
+++ b/ee/config/audit_events/types/deleted_group_audit_event_streaming_destination.yml
@@ -0,0 +1,9 @@
+name: deleted_group_audit_event_streaming_destination
+description: Event triggered when an external audit event destination for a top-level group is deleted.
+introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/436610
+introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148738
+feature_category: audit_events
+milestone: "16.11"
+saved_to_database: true
+streamed: true
+scope: [Group]
diff --git a/ee/spec/requests/api/graphql/mutations/audit_events/group/audit_event_streaming_destinations/delete_spec.rb b/ee/spec/requests/api/graphql/mutations/audit_events/group/audit_event_streaming_destinations/delete_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..231b76558b6ff245479c33b80fa508c61dc94b25
--- /dev/null
+++ b/ee/spec/requests/api/graphql/mutations/audit_events/group/audit_event_streaming_destinations/delete_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Delete external audit event destinations for groups', feature_category: :audit_events do
+ include GraphqlHelpers
+
+ let_it_be(:destination) { create(:audit_events_group_external_streaming_destination) }
+ let_it_be(:group) { destination.group }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:mutation) { graphql_mutation(:group_audit_event_streaming_destinations_delete, id: global_id_of(destination)) }
+ let(:mutation_response) { graphql_mutation_response(:group_audit_event_streaming_destinations_delete) }
+
+ subject(:mutate) { post_graphql_mutation(mutation, current_user: current_user) }
+
+ context 'when feature is licensed' do
+ before do
+ stub_licensed_features(external_audit_events: true)
+ end
+
+ context 'when current user is a group owner' do
+ before_all do
+ group.add_owner(current_user)
+ end
+
+ it 'destroys the destination' do
+ expect { mutate }.to change { AuditEvents::Group::ExternalStreamingDestination.count }.by(-1)
+ end
+
+ it 'audits the deletion' do
+ expected_hash = {
+ name: 'deleted_group_audit_event_streaming_destination',
+ author: current_user,
+ scope: group,
+ target: group,
+ message: 'Deleted audit event streaming destination for HTTP',
+ additional_details: {
+ id: destination.id,
+ category: destination.category
+ }
+ }
+
+ expect(Gitlab::Audit::Auditor).to receive(:audit).with(hash_including(expected_hash))
+
+ mutate
+ end
+
+ context 'when there is an error during destroy' do
+ before do
+ expect_next_found_instance_of(AuditEvents::Group::ExternalStreamingDestination) do |destination|
+ allow(destination).to receive(:destroy).and_return(false)
+ errors = ActiveModel::Errors.new(destination).tap { |e| e.add(:base, 'error message') }
+ allow(destination).to receive(:errors).and_return(errors)
+ end
+ end
+
+ it 'does not destroy the destination and returns the error' do
+ expect { mutate }.not_to change { AuditEvents::Group::ExternalStreamingDestination.count }
+
+ expect(mutation_response).to include('errors' => ['error message'])
+ end
+ end
+ end
+
+ context 'when current user is a group maintainer' do
+ before_all do
+ group.add_maintainer(current_user)
+ end
+
+ it_behaves_like 'a mutation on an unauthorized resource'
+ end
+ end
+
+ context 'when feature is unlicensed' do
+ before do
+ stub_licensed_features(external_audit_events: false)
+ end
+
+ it_behaves_like 'a mutation on an unauthorized resource'
+ end
+end