Skip to content
Snippets Groups Projects
Verified Commit 7045aff7 authored by Jarka Košanová's avatar Jarka Košanová :palm_tree: Committed by GitLab
Browse files

Merge branch '436613-instance-namespace-apis' into 'master'

Adding create and delete apis for instance namespace filters

See merge request !153156



Merged-by: Jarka Košanová's avatarJarka Košanová <jarka@gitlab.com>
Approved-by: default avatarEvan Read <eread@gitlab.com>
Approved-by: Jarka Košanová's avatarJarka Košanová <jarka@gitlab.com>
Reviewed-by: Jarka Košanová's avatarJarka Košanová <jarka@gitlab.com>
Reviewed-by: default avatarEvan Read <eread@gitlab.com>
Reviewed-by: default avatarHitesh Raghuvanshi <hraghuvanshi@gitlab.com>
Co-authored-by: default avatarHitesh Raghuvanshi <hraghuvanshi@gitlab.com>
parents 1d4e36ab cd588f87
No related branches found
No related tags found
3 merge requests!162537Backport 17-1: Handle empty ff merge in from train ref strategy,!162233Draft: Script to update Topology Service Gem,!153156Adding create and delete apis for instance namespace filters
Pipeline #1338747810 passed
Showing
with 477 additions and 1 deletion
......@@ -2018,6 +2018,52 @@ Input type: `AuditEventsInstanceDestinationEventsDeleteInput`
| <a id="mutationauditeventsinstancedestinationeventsdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationauditeventsinstancedestinationeventsdeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
 
### `Mutation.auditEventsInstanceDestinationNamespaceFilterCreate`
DETAILS:
**Introduced** in GitLab 17.2.
**Status**: Experiment.
Input type: `AuditEventsInstanceDestinationNamespaceFilterCreateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationauditeventsinstancedestinationnamespacefiltercreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationauditeventsinstancedestinationnamespacefiltercreatedestinationid"></a>`destinationId` | [`AuditEventsInstanceExternalStreamingDestinationID!`](#auditeventsinstanceexternalstreamingdestinationid) | Destination ID. |
| <a id="mutationauditeventsinstancedestinationnamespacefiltercreatenamespacepath"></a>`namespacePath` | [`String`](#string) | Full path of the namespace. Project or group namespaces only. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationauditeventsinstancedestinationnamespacefiltercreateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationauditeventsinstancedestinationnamespacefiltercreateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationauditeventsinstancedestinationnamespacefiltercreatenamespacefilter"></a>`namespaceFilter` | [`InstanceAuditEventNamespaceFilter`](#instanceauditeventnamespacefilter) | Namespace filter to be created. |
### `Mutation.auditEventsInstanceDestinationNamespaceFilterDelete`
DETAILS:
**Introduced** in GitLab 17.2.
**Status**: Experiment.
Input type: `AuditEventsInstanceDestinationNamespaceFilterDeleteInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationauditeventsinstancedestinationnamespacefilterdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationauditeventsinstancedestinationnamespacefilterdeletenamespacefilterid"></a>`namespaceFilterId` | [`AuditEventsInstanceNamespaceFilterID!`](#auditeventsinstancenamespacefilterid) | Namespace filter ID. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationauditeventsinstancedestinationnamespacefilterdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationauditeventsinstancedestinationnamespacefilterdeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
### `Mutation.auditEventsStreamingDestinationEventsAdd`
 
Input type: `AuditEventsStreamingDestinationEventsAddInput`
......@@ -23392,6 +23438,18 @@ Stores instance level Amazon S3 configurations for audit event streaming.
| <a id="instanceamazons3configurationtypeid"></a>`id` | [`ID!`](#id) | ID of the configuration. |
| <a id="instanceamazons3configurationtypename"></a>`name` | [`String!`](#string) | Name of the external destination to send audit events to. |
 
### `InstanceAuditEventNamespaceFilter`
Represents a subgroup or project filter that belongs to an instance level external audit event streaming destination.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="instanceauditeventnamespacefilterexternalstreamingdestination"></a>`externalStreamingDestination` | [`InstanceAuditEventStreamingDestination!`](#instanceauditeventstreamingdestination) | Destination to which the filter belongs. |
| <a id="instanceauditeventnamespacefilterid"></a>`id` | [`ID!`](#id) | ID of the filter. |
| <a id="instanceauditeventnamespacefilternamespace"></a>`namespace` | [`Namespace!`](#namespace) | Group or project namespace the filter belongs to. |
### `InstanceAuditEventStreamingDestination`
 
Represents an external destination to stream instance level audit events.
......@@ -23405,6 +23463,7 @@ Represents an external destination to stream instance level audit events.
| <a id="instanceauditeventstreamingdestinationeventtypefilters"></a>`eventTypeFilters` | [`[String!]!`](#string) | List of event type filters added for streaming. |
| <a id="instanceauditeventstreamingdestinationid"></a>`id` | [`ID!`](#id) | ID of the destination. |
| <a id="instanceauditeventstreamingdestinationname"></a>`name` | [`String!`](#string) | Name of the external destination to send audit events to. |
| <a id="instanceauditeventstreamingdestinationnamespacefilters"></a>`namespaceFilters` | [`[InstanceAuditEventNamespaceFilter!]`](#instanceauditeventnamespacefilter) | List of subgroup or project filters for the destination. |
 
### `InstanceExternalAuditEventDestination`
 
......@@ -36280,6 +36339,12 @@ A `AuditEventsInstanceGoogleCloudLoggingConfigurationID` is a global ID. It is e
 
An example `AuditEventsInstanceGoogleCloudLoggingConfigurationID` is: `"gid://gitlab/AuditEvents::Instance::GoogleCloudLoggingConfiguration/1"`.
 
### `AuditEventsInstanceNamespaceFilterID`
A `AuditEventsInstanceNamespaceFilterID` is a global ID. It is encoded as a string.
An example `AuditEventsInstanceNamespaceFilterID` is: `"gid://gitlab/AuditEvents::Instance::NamespaceFilter/1"`.
### `AuditEventsStreamingHTTPNamespaceFilterID`
 
A `AuditEventsStreamingHTTPNamespaceFilterID` is a global ID. It is encoded as a string.
......@@ -68,10 +68,12 @@ Audit event types belong to the following product categories.
| [`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 |
| [`created_group_namespace_filter`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150712) | 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 [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/436612) | Group |
| [`created_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 |
| [`created_instance_namespace_filter`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/153156) | Event triggered when a namespace filter for an external audit event destination for an instance is created.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.2](https://gitlab.com/gitlab-org/gitlab/-/issues/436613) | Instance |
| [`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 |
| [`deleted_group_namespace_filter`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/150712) | 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 [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/436612) | Group |
| [`deleted_instance_audit_event_streaming_destination`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/14910) | Event triggered when an external audit event destination for a GitLab instance is deleted.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.11](https://gitlab.com/gitlab-org/gitlab/-/issues/436615) | Instance |
| [`deleted_instance_namespace_filter`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/153156) | Event triggered when a namespace filter for an external audit event destination for an instance is deleted.| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.2](https://gitlab.com/gitlab-org/gitlab/-/issues/436613) | Instance |
| [`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 |
......
......@@ -202,6 +202,10 @@ def self.authorization_scopes
alpha: { milestone: '17.0' }
mount_mutation ::Mutations::AuditEvents::Group::NamespaceFilters::Delete,
alpha: { milestone: '17.0' }
mount_mutation ::Mutations::AuditEvents::Instance::NamespaceFilters::Create,
alpha: { milestone: '17.2' }
mount_mutation ::Mutations::AuditEvents::Instance::NamespaceFilters::Delete,
alpha: { milestone: '17.2' }
prepend(Types::DeprecatedMutations)
end
......
# frozen_string_literal: true
module Mutations
module AuditEvents
module Instance
module NamespaceFilters
# rubocop:disable GraphQL/GraphqlName -- This is a base mutation so name is not needed here
class Base < BaseMutation
authorize :admin_instance_external_audit_events
private
def audit(filter, action:)
audit_context = {
name: "#{action}_instance_namespace_filter",
author: current_user,
scope: Gitlab::Audit::InstanceScope.new,
target: filter.external_streaming_destination,
message: "#{action.capitalize} namespace filter for instance audit event streaming destination.",
additional_details: {
destination_name: filter.external_streaming_destination.name,
namespace: filter.namespace.full_path
}
}
::Gitlab::Audit::Auditor.audit(audit_context)
end
end
end
end
end
end
# rubocop:enable GraphQL/GraphqlName
# frozen_string_literal: true
module Mutations
module AuditEvents
module Instance
module NamespaceFilters
class Create < Base
graphql_name 'AuditEventsInstanceDestinationNamespaceFilterCreate'
argument :destination_id, ::Types::GlobalIDType[::AuditEvents::Instance::ExternalStreamingDestination],
required: true,
description: 'Destination ID.'
argument :namespace_path, GraphQL::Types::String,
required: false,
description: 'Full path of the namespace. Project or group namespaces only.'
field :namespace_filter, ::Types::AuditEvents::Instance::NamespaceFilterType,
null: true,
description: 'Namespace filter to be created.'
def resolve(args)
destination = authorized_find!(args[:destination_id])
namespace = namespace(args[:namespace_path])
filter = ::AuditEvents::Instance::NamespaceFilter.new(external_streaming_destination: destination,
namespace: namespace)
audit(filter, action: :created) if filter.save
{ namespace_filter: (filter if filter.persisted?), errors: Array(filter.errors) }
end
private
def find_object(destination_id)
::GitlabSchema.object_from_id(destination_id,
expected_type: ::AuditEvents::Instance::ExternalStreamingDestination)
end
def namespace(namespace_path)
namespace = Routable.find_by_full_path(namespace_path)
case namespace
when ::Group
namespace
when ::Project
namespace.project_namespace
else
raise Gitlab::Graphql::Errors::ArgumentError, "namespace_path should be of group or project only."
end
end
end
end
end
end
end
# frozen_string_literal: true
module Mutations
module AuditEvents
module Instance
module NamespaceFilters
class Delete < Base
graphql_name 'AuditEventsInstanceDestinationNamespaceFilterDelete'
argument :namespace_filter_id, ::Types::GlobalIDType[::AuditEvents::Instance::NamespaceFilter],
required: true,
description: 'Namespace filter ID.'
def resolve(namespace_filter_id:)
filter = authorized_find!(id: namespace_filter_id)
audit(filter, action: :deleted) if filter.destroy
{ namespace_filter: nil, errors: filter.errors }
end
end
end
end
end
end
# frozen_string_literal: true
module Types
module AuditEvents
module Instance
class NamespaceFilterType < ::Types::BaseObject
graphql_name 'InstanceAuditEventNamespaceFilter'
description 'Represents a subgroup or project filter that belongs to ' \
'an instance level external audit event streaming destination.'
authorize :admin_instance_external_audit_events
field :id, GraphQL::Types::ID,
null: false,
description: 'ID of the filter.'
field :namespace, ::Types::NamespaceType,
null: false,
description: 'Group or project namespace the filter belongs to.'
field :external_streaming_destination, ::Types::AuditEvents::Instance::StreamingDestinationType,
null: false,
description: 'Destination to which the filter belongs.'
end
end
end
end
......@@ -9,6 +9,10 @@ class StreamingDestinationType < ::Types::BaseObject
authorize :admin_instance_external_audit_events
implements AuditEventStreamingDestinationInterface
field :namespace_filters, [::Types::AuditEvents::Instance::NamespaceFilterType],
null: true,
description: 'List of subgroup or project filters for the destination.'
end
end
end
......
# frozen_string_literal: true
module AuditEvents
module Instance
class NamespaceFilterPolicy < ::BasePolicy
delegate { :global }
end
end
end
name: created_instance_namespace_filter
description: Event triggered when a namespace filter for an external audit event destination for an instance is created.
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/436613
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/153156
feature_category: audit_events
milestone: "17.2"
saved_to_database: true
streamed: true
scope: [Instance]
name: deleted_instance_namespace_filter
description: Event triggered when a namespace filter for an external audit event destination for an instance is deleted.
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/436613
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/153156
feature_category: audit_events
milestone: "17.2"
saved_to_database: true
streamed: true
scope: [Instance]
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['InstanceAuditEventNamespaceFilter'], feature_category: :audit_events do
let(:fields) do
%i[id namespace external_streaming_destination]
end
specify { expect(described_class.graphql_name).to eq('InstanceAuditEventNamespaceFilter') }
specify { expect(described_class).to have_graphql_fields(fields) }
end
......@@ -4,7 +4,7 @@
RSpec.describe GitlabSchema.types['InstanceAuditEventStreamingDestination'], feature_category: :audit_events do
let(:fields) do
%i[id name category config event_type_filters]
%i[id name category config event_type_filters namespace_filters]
end
specify { expect(described_class.graphql_name).to eq('InstanceAuditEventStreamingDestination') }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Create a namespace filter for instance level external audit event destinations', feature_category: :audit_events do
include GraphqlHelpers
let_it_be(:destination) { create(:audit_events_instance_external_streaming_destination) }
let_it_be(:current_user) { create(:user) }
let(:mutation) { graphql_mutation(:audit_events_instance_destination_namespace_filter_create, input) }
let(:mutation_response) { graphql_mutation_response(:audit_events_instance_destination_namespace_filter_create) }
subject { 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 instance admin' do
let(:current_user) { create(:admin) }
shared_examples 'creation of namespace filters' do
context 'when namespace_path is valid' do
let(:input) do
{
destinationId: destination.to_gid,
namespacePath: namespace.full_path
}
end
it 'creates a namespace filter', :aggregate_failures do
expect(::Gitlab::Audit::Auditor).to receive(:audit).with(a_hash_including(
name: 'created_instance_namespace_filter',
author: current_user,
scope: an_instance_of(Gitlab::Audit::InstanceScope),
target: destination,
message: "Created namespace filter for instance audit event streaming destination.",
additional_details: {
destination_name: destination.name,
namespace: namespace.full_path
}
)).once.and_call_original
expect { subject }
.to change { AuditEvent.count }.by(1)
namespace_filters = destination.namespace_filters
expect(namespace_filters.first.namespace).to eq(namespace)
expect(namespace_filters.first.external_streaming_destination).to eq(destination)
expect_graphql_errors_to_be_empty
expect(mutation_response['errors']).to be_empty
expect(mutation_response).to have_key('namespaceFilter')
expect(mutation_response['namespaceFilter']['namespace']['fullPath']).to eq(namespace.full_path)
expect(mutation_response['namespaceFilter']['externalStreamingDestination']['name'])
.to eq(destination.name)
end
context 'when namespace filter for the given namespace already exists' do
before do
create(:audit_events_streaming_instance_namespace_filters,
external_streaming_destination: destination,
namespace: namespace
)
end
it 'returns error' do
expect { subject }.not_to change { AuditEvents::Instance::NamespaceFilter.count }
expect(mutation_response['errors']).to match_array(['Namespace has already been taken'])
expect(mutation_response['namespaceFilter']).to be nil
end
end
end
context 'when given namespace path is invalid' do
let(:input) do
{
destinationId: destination.to_gid,
namespace_path: 'invalid_path'
}
end
it 'returns error' do
expect { subject }.not_to change { AuditEvents::Instance::NamespaceFilter.count }
expect(graphql_errors)
.to include(a_hash_including('message' => "namespace_path should be of group or project only."))
expect(mutation_response).to eq(nil)
end
end
end
context 'when group_path is passed in params' do
it_behaves_like 'creation of namespace filters' do
let_it_be(:namespace) { create(:group) }
end
end
context 'when project_path is passed in params' do
it_behaves_like 'creation of namespace filters' do
let_it_be(:project) { create(:project, group: create(:group)) }
let_it_be(:namespace) { project.project_namespace }
end
end
context 'when namespace_path is invalid' do
let(:input) do
{
destinationId: destination.to_gid,
namespace_path: 'invalid_path'
}
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ['namespace_path should be of group or project only.']
end
end
context 'when current user is not instance admin' do
let_it_be(:namespace_group) { create(:group) }
let(:input) do
{
destinationId: destination.to_gid,
namespacePath: namespace_group.full_path
}
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
let_it_be(:namespace_group) { create(:group) }
let(:input) do
{
destinationId: destination.to_gid,
namespacePath: namespace_group.full_path
}
end
it_behaves_like 'a mutation on an unauthorized resource'
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Delete a namespace filter for instance level external audit event destinations', feature_category: :audit_events do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:destination) { create(:audit_events_instance_external_streaming_destination) }
let_it_be(:filter) do
create(:audit_events_streaming_instance_namespace_filters, external_streaming_destination: destination,
namespace: group)
end
let(:mutation) { graphql_mutation(:audit_events_instance_destination_namespace_filter_delete, input) }
let(:mutation_response) { graphql_mutation_response(:audit_events_instance_destination_namespace_filter_delete) }
let(:input) do
{ namespaceFilterId: filter.to_gid }
end
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 instance admin' do
let_it_be(:current_user) { create(:admin) }
context 'when namespace filter id is valid' do
it 'deletes the filter', :aggregate_failures do
expect(::Gitlab::Audit::Auditor).to receive(:audit).with(a_hash_including(
name: 'deleted_instance_namespace_filter',
author: current_user,
scope: an_instance_of(Gitlab::Audit::InstanceScope),
target: destination,
message: "Deleted namespace filter for instance audit event streaming destination."))
.once.and_call_original
expect { mutate }.to change { AuditEvents::Instance::NamespaceFilter.count }.by(-1)
expect(destination.reload.namespace_filters).to be_empty
expect_graphql_errors_to_be_empty
expect(mutation_response['errors']).to be_empty
expect(mutation_response['namespaceFilter']).to be nil
end
end
context 'when namespace filter id is invalid' do
let(:input) do
{ namespaceFilterId: 'gid://gitlab/AuditEvents::Instance::NamespaceFilter/invalid_id' }
end
it_behaves_like 'a mutation that returns a top-level access error'
end
end
context 'when current user is not instance admin' do
it_behaves_like 'a mutation that returns a top-level access error'
end
end
context 'when feature is not licensed' do
before do
stub_licensed_features(external_audit_events: false)
end
it_behaves_like 'a mutation on an unauthorized resource'
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment