diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb index 8b020079235db3c41e363a32db5a8c27ecc25589..9194690ce82569a0cfa0cfd569fd4bbea0453c03 100644 --- a/app/controllers/projects/registry/repositories_controller.rb +++ b/app/controllers/projects/registry/repositories_controller.rb @@ -57,3 +57,5 @@ def ensure_root_container_repository! end end end + +Projects::Registry::RepositoriesController.prepend_mod diff --git a/app/graphql/mutations/container_repositories/destroy.rb b/app/graphql/mutations/container_repositories/destroy.rb index af9285ef3ff7da04219c280073f69347af342be4..a1ee8188fa6a61e978ae8ba9eb9f40813dbc48f9 100644 --- a/app/graphql/mutations/container_repositories/destroy.rb +++ b/app/graphql/mutations/container_repositories/destroy.rb @@ -20,7 +20,7 @@ class Destroy < ::Mutations::ContainerRepositories::DestroyBase def resolve(id:) container_repository = authorized_find!(id: id) - container_repository.delete_scheduled! + container_repository.delete_scheduled! && audit_event(container_repository) track_event(:delete_repository, :container) @@ -29,6 +29,14 @@ def resolve(id:) errors: [] } end + + private + + def audit_event(repository) + # defined in EE + end end end end + +Mutations::ContainerRepositories::Destroy.prepend_mod diff --git a/app/workers/container_registry/delete_container_repository_worker.rb b/app/workers/container_registry/delete_container_repository_worker.rb index d6ecd836ed2081709f4a3e06f7e0a85d8e3543cf..95808bd6bbc84e57b750bb063b166690c362849e 100644 --- a/app/workers/container_registry/delete_container_repository_worker.rb +++ b/app/workers/container_registry/delete_container_repository_worker.rb @@ -32,6 +32,8 @@ def perform_work end next_container_repository.destroy! + + audit_event(next_container_repository) rescue StandardError => exception next_container_repository&.set_delete_scheduled_status @@ -78,5 +80,11 @@ def log_delete_tags_service_result(container_repository, delete_tags_service_res ) ) end + + def audit_event(repository) + # defined in EE + end end end + +ContainerRegistry::DeleteContainerRepositoryWorker.prepend_mod diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md index d42185b7c18bd0fef88324a231d2ac53b21c96d8..29372182a2888a394c44d3a32d6ce9674e31acf0 100644 --- a/doc/user/compliance/audit_event_types.md +++ b/doc/user/compliance/audit_event_types.md @@ -184,6 +184,13 @@ Audit event types belong to the following product categories. | [`update_compliance_framework`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74292) | Triggered when a compliance framework is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/340649) | Group | | [`update_status_check`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84624) | Event triggered when an external status check is updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/355805) | Project | +### Container registry + +| Name | Description | Saved to database | Streamed | Introduced in | Scope | +|:------------|:------------|:------------------|:---------|:--------------|:--------------| +| [`container_repository_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/152967) | Triggered when a project's container registry is deleted| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/362290) | Project | +| [`container_repository_deletion_marked`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/152967) | Triggered when a project's container repository is marked for deletion| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.1](https://gitlab.com/gitlab-org/gitlab/-/issues/362290) | Project | + ### Continuous delivery | Name | Description | Saved to database | Streamed | Introduced in | Scope | diff --git a/ee/app/controllers/ee/projects/registry/repositories_controller.rb b/ee/app/controllers/ee/projects/registry/repositories_controller.rb new file mode 100644 index 0000000000000000000000000000000000000000..dcd83d0bb772980f47da310fa2aacb4ace1a72b5 --- /dev/null +++ b/ee/app/controllers/ee/projects/registry/repositories_controller.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module EE + module Projects + module Registry + module RepositoriesController + extend ::Gitlab::Utils::Override + + override :destroy + def destroy + super + + audit_destroy_marked_event + end + + private + + def audit_destroy_marked_event + message = "Marked container repository #{image.id} for deletion" + audit_context = { + name: 'container_repository_deletion_marked', + author: current_user || ::Gitlab::Audit::UnauthenticatedAuthor.new, + scope: project, + target: image, + message: message + } + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end + end +end diff --git a/ee/app/graphql/ee/mutations/container_repositories/destroy.rb b/ee/app/graphql/ee/mutations/container_repositories/destroy.rb new file mode 100644 index 0000000000000000000000000000000000000000..7f46a94d283730377b858502c33685a8d6b45a4a --- /dev/null +++ b/ee/app/graphql/ee/mutations/container_repositories/destroy.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module EE + module Mutations + module ContainerRepositories # rubocop:disable Gitlab/BoundedContexts -- fix in FOSS class + module Destroy + extend ::Gitlab::Utils::Override + + private + + override :audit_event + def audit_event(repository) + message = "Marked container repository #{repository.id} for deletion" + context = { + name: 'container_repository_deletion_marked', + author: current_user, + scope: repository.project, + target: repository, + message: message + } + + ::Gitlab::Audit::Auditor.audit(context) + end + end + end + end +end diff --git a/ee/app/workers/ee/container_registry/delete_container_repository_worker.rb b/ee/app/workers/ee/container_registry/delete_container_repository_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..989cc4efb838492e5b061f6233840b9d7a2c6e08 --- /dev/null +++ b/ee/app/workers/ee/container_registry/delete_container_repository_worker.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module EE + module ContainerRegistry + module DeleteContainerRepositoryWorker + extend ::Gitlab::Utils::Override + + private + + override :audit_event + def audit_event(repository) + audit_context = { + name: "container_repository_deleted", + author: ::Gitlab::Audit::UnauthenticatedAuthor.new(name: '(System)'), + scope: repository.project, + target: repository, + message: "Container repository #{repository.id} deleted by worker" + } + + ::Gitlab::Audit::Auditor.audit(audit_context) + end + end + end +end diff --git a/ee/config/audit_events/types/container_repository_deleted.yml b/ee/config/audit_events/types/container_repository_deleted.yml new file mode 100644 index 0000000000000000000000000000000000000000..3a709a13945b3b258b1b47659517bcdb644ad61e --- /dev/null +++ b/ee/config/audit_events/types/container_repository_deleted.yml @@ -0,0 +1,10 @@ +--- +name: container_repository_deleted +description: Triggered when a project's container registry is deleted +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/362290 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/152967 +feature_category: container_registry +milestone: '17.1' +saved_to_database: true +streamed: true +scope: [Project] diff --git a/ee/config/audit_events/types/container_repository_deletion_marked.yml b/ee/config/audit_events/types/container_repository_deletion_marked.yml new file mode 100644 index 0000000000000000000000000000000000000000..49eec1c59aea77074a12ca2de7d29f245b258b6b --- /dev/null +++ b/ee/config/audit_events/types/container_repository_deletion_marked.yml @@ -0,0 +1,10 @@ +--- +name: container_repository_deletion_marked +description: Triggered when a project's container repository is marked for deletion +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/362290 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/152967 +feature_category: container_registry +milestone: '17.1' +saved_to_database: true +streamed: true +scope: [Project] diff --git a/ee/spec/controllers/projects/registry/repositories_controller_spec.rb b/ee/spec/controllers/projects/registry/repositories_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9259a4204df443c2d09c4c6e87a348b03f2cfd93 --- /dev/null +++ b/ee/spec/controllers/projects/registry/repositories_controller_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::Registry::RepositoriesController, feature_category: :container_registry do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, group: group) } + let_it_be(:user) { create(:user) } + let_it_be(:repository) { create(:container_repository, :root, project: project) } + + describe '#destroy' do + before_all do + project.add_maintainer(user) + end + + before do + sign_in(user) + + stub_container_registry_config(enabled: true) + stub_container_registry_info + + stub_licensed_features(external_audit_events: true) + group.external_audit_event_destinations.create!(destination_url: 'http://example.com') + end + + subject(:destroy_repo) do + delete :destroy, params: { + namespace_id: project.namespace, project_id: project, id: repository + }, format: :json + end + + it 'creates an audit event' do + expected_message = "Marked container repository #{repository.id} for deletion" + + expect { destroy_repo }.to change { AuditEvent.count }.by(1) + + expect(AuditEvent.last.details[:custom_message]).to eq(expected_message) + end + + it_behaves_like 'sends correct event type in audit event stream' do + let_it_be(:event_type) { 'container_repository_deletion_marked' } + end + end +end diff --git a/ee/spec/graphql/ee/mutations/container_repositories/destroy_spec.rb b/ee/spec/graphql/ee/mutations/container_repositories/destroy_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..cc5a0586a8a1cd1c7799f941a46257ee75598f0a --- /dev/null +++ b/ee/spec/graphql/ee/mutations/container_repositories/destroy_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::ContainerRepositories::Destroy, feature_category: :container_registry do + using RSpec::Parameterized::TableSyntax + + let_it_be_with_reload(:container_repository) { create(:container_repository) } + let_it_be(:user) { create(:user) } + + describe '#resolve' do + subject do + described_class.new(object: nil, context: { current_user: user }, field: nil) + .resolve(id: container_repository.to_global_id) + end + + before do + container_repository.project.send(:add_maintainer, user) + end + + include_examples 'audit event logging' do + let(:operation) { subject } + let(:event_type) { 'container_repository_deletion_marked' } + let(:fail_condition!) do + # rubocop:disable RSpec/AnyInstanceOf -- not next instance + allow_any_instance_of(ContainerRepository).to receive(:delete_scheduled!).and_return(false) + # rubocop:enable RSpec/AnyInstanceOf + end + + let(:author) { user } + + let(:attributes) do + { + author_id: author.id, + entity_id: container_repository.project.id, + entity_type: 'Project', + details: { + event_name: "container_repository_deletion_marked", + author_class: author.class.to_s, + author_name: author.name, + custom_message: "Marked container repository #{container_repository.id} for deletion", + target_details: container_repository.name, + target_id: container_repository.id, + target_type: container_repository.class.to_s + } + } + end + end + end +end diff --git a/ee/spec/workers/ee/container_registry/delete_container_repository_worker_spec.rb b/ee/spec/workers/ee/container_registry/delete_container_repository_worker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..8a74bcef27e927842e653c1911a72a2b9614dbfa --- /dev/null +++ b/ee/spec/workers/ee/container_registry/delete_container_repository_worker_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ContainerRegistry::DeleteContainerRepositoryWorker, :aggregate_failures, feature_category: :container_registry do + let_it_be_with_reload(:container_repository) { create(:container_repository) } + + let(:worker) { described_class.new } + + before do + stub_container_registry_config(enabled: true) + + stub_container_registry_tags( + repository: container_repository.path, + tags: [] + ) + end + + describe '#perform_work' do + before do + container_repository.delete_scheduled! + end + + include_examples 'audit event logging' do + let(:operation) { worker.perform_work } + let(:event_type) { 'container_repository_deleted' } + let(:fail_condition!) do + allow(ContainerRepository).to receive(:next_pending_destruction).and_return(nil) + end + + let(:author) { ::Gitlab::Audit::UnauthenticatedAuthor.new(name: '(System)') } + + let(:attributes) do + { + author_id: author.id, + entity_id: container_repository.project.id, + entity_type: 'Project', + details: { + event_name: "container_repository_deleted", + author_class: author.class.to_s, + author_name: author.name, + custom_message: "Container repository #{container_repository.id} deleted by worker", + target_details: container_repository.name, + target_id: container_repository.id, + target_type: container_repository.class.to_s + } + } + end + end + end +end