diff --git a/ee/app/controllers/ee/projects/deploy_tokens_controller.rb b/ee/app/controllers/ee/projects/deploy_tokens_controller.rb index 8d1c80d793b1729ed58bc9efdfb167a36d899f4f..71679814c4954ed14dd8bdb52239e5ca0bde6c42 100644 --- a/ee/app/controllers/ee/projects/deploy_tokens_controller.rb +++ b/ee/app/controllers/ee/projects/deploy_tokens_controller.rb @@ -17,16 +17,17 @@ def revoke def log_audit_event # rubocop:disable Gitlab/ModuleWithInstanceVariables message = "Revoked project deploy token with name: #{@token.name} with token_id: #{@token.id} with scopes: #{@token.scopes}." - - ::AuditEventService.new( - current_user, - @project, - target_id: @token.id, - target_type: @token.class.name, - target_details: @token.name, - action: :custom, - custom_message: message - ).security_event + audit_context = { + name: 'deploy_token_revoked', + author: current_user, + scope: @project, + target: @token, + message: message, + additional_details: { + action: :custom + } + } + ::Gitlab::Audit::Auditor.audit(audit_context) # rubocop:enable Gitlab/ModuleWithInstanceVariables end end diff --git a/ee/app/services/audit_events/build_service.rb b/ee/app/services/audit_events/build_service.rb index 96699ae9d4a84d3dd1f3243a5d5b8b246a6bf69a..c9ef0165d5bb74eedb7f99a96ab2597f45791a9e 100644 --- a/ee/app/services/audit_events/build_service.rb +++ b/ee/app/services/audit_events/build_service.rb @@ -8,7 +8,7 @@ class BuildService # @raise [MissingAttributeError] when required attributes are blank # # @return [BuildService] - def initialize(author:, scope:, target:, message:, created_at: DateTime.current) + def initialize(author:, scope:, target:, message:, created_at: DateTime.current, additional_details: {}) raise MissingAttributeError if missing_attribute?(author, scope, target, message) @author = build_author(author) @@ -17,6 +17,7 @@ def initialize(author:, scope:, target:, message:, created_at: DateTime.current) @ip_address = build_ip_address @message = build_message(message) @created_at = created_at + @additional_details = additional_details end # Create an instance of AuditEvent @@ -64,7 +65,7 @@ def base_details_payload target_type: @target.type, target_details: @target.details, custom_message: @message - } + }.merge(@additional_details) end def build_author(author) diff --git a/ee/app/services/ee/projects/deploy_tokens/create_service.rb b/ee/app/services/ee/projects/deploy_tokens/create_service.rb index 1dd762e9f13b0d9b649ac3957be084694b1a5595..d841938c172fb2d69ba104a90b2d854f7d4cad73 100644 --- a/ee/app/services/ee/projects/deploy_tokens/create_service.rb +++ b/ee/app/services/ee/projects/deploy_tokens/create_service.rb @@ -16,21 +16,25 @@ def execute private def audit_event_service(deploy_token, result) - message = if result[:status] == :success - "Created project deploy token with name: #{deploy_token.name} with token_id: #{deploy_token.id} with scopes: #{deploy_token.scopes}." - else - "Attempted to create project deploy token but failed with message: #{result[:message]}" - end + if result[:status] == :success + message = "Created project deploy token with name: #{deploy_token.name} with token_id: #{deploy_token.id} with scopes: #{deploy_token.scopes}." + name = "deploy_token_created" + else + message = "Attempted to create project deploy token but failed with message: #{result[:message]}" + name = "deploy_token_creation_failed" + end - ::AuditEventService.new( - current_user, - project, - target_id: deploy_token.id, - target_type: deploy_token.class.name, - target_details: deploy_token.name, - action: :custom, - custom_message: message - ).security_event + audit_context = { + name: name, + author: current_user, + scope: project, + target: deploy_token, + message: message, + additional_details: { + action: :custom + } + } + ::Gitlab::Audit::Auditor.audit(audit_context) end end end diff --git a/ee/app/services/ee/projects/deploy_tokens/destroy_service.rb b/ee/app/services/ee/projects/deploy_tokens/destroy_service.rb index a7d57980d1a06dfc4250082304b70604a0462998..08514fb8f6dfe1e9a91e57f999c14719eac7e942 100644 --- a/ee/app/services/ee/projects/deploy_tokens/destroy_service.rb +++ b/ee/app/services/ee/projects/deploy_tokens/destroy_service.rb @@ -17,16 +17,17 @@ def execute def audit_event_service(deploy_token) message = "Destroyed project deploy token with name: #{deploy_token.name} with token_id: #{deploy_token.id} with scopes: #{deploy_token.scopes}." - - ::AuditEventService.new( - current_user, - project, - target_id: deploy_token.id, - target_type: deploy_token.class.name, - target_details: deploy_token.name, - action: :custom, - custom_message: message - ).security_event + audit_context = { + name: 'deploy_token_destroyed', + author: current_user, + scope: project, + target: deploy_token, + message: message, + additional_details: { + action: :custom + } + } + ::Gitlab::Audit::Auditor.audit(audit_context) end end end diff --git a/ee/lib/gitlab/audit/auditor.rb b/ee/lib/gitlab/audit/auditor.rb index b4968bd8b3142b21a642d2843f725003f5f116d4..bb63b573c56d72872144f40e64ccdf8668865d2e 100644 --- a/ee/lib/gitlab/audit/auditor.rb +++ b/ee/lib/gitlab/audit/auditor.rb @@ -11,6 +11,7 @@ class Auditor # @option context [User, Project, Group] :scope the scope which audit event belongs to # @option context [Object] :target the target object being audited # @option context [String] :message the message describing the action + # @option context [Hash] :additional_details the additional details we want to merge into audit event details. # @option context [Time] :created_at the time that the event occurred (defaults to the current time) # # @example Using block (useful when events are emitted deep in the call stack) @@ -60,6 +61,7 @@ def initialize(context = {}) @target = @context.fetch(:target) @created_at = @context.fetch(:created_at, DateTime.current) @message = @context.fetch(:message, '') + @additional_details = @context.fetch(:additional_details, {}) end def multiple_audit @@ -98,7 +100,8 @@ def build_event(message) scope: @scope, target: @target, created_at: @created_at, - message: message + message: message, + additional_details: @additional_details ).execute end diff --git a/ee/spec/lib/gitlab/audit/auditor_spec.rb b/ee/spec/lib/gitlab/audit/auditor_spec.rb index 62b527bd2d99c2b1cfdc813977b275e6f6e9bd2e..8a3603c3b2f9458fa975388ccb4e0d86e0c9711f 100644 --- a/ee/spec/lib/gitlab/audit/auditor_spec.rb +++ b/ee/spec/lib/gitlab/audit/auditor_spec.rb @@ -154,6 +154,40 @@ end end + context 'when overriding the additional_details' do + additional_details = { action: :custom } + let(:context) do + { name: name, + author: author, + scope: scope, + target: target, + created_at: Time.zone.now, + additional_details: additional_details } + end + + it 'logs audit events to database' do + freeze_time do + audit! + + expect(AuditEvent.last.details).to include(additional_details) + end + end + + it 'logs audit events to file' do + freeze_time do + expect(::Gitlab::AuditJsonLogger).to receive(:build).and_return(logger) + + audit! + + expect(logger).to have_received(:info).exactly(2).times.with( + hash_including( + 'details' => hash_including('action' => 'custom') + ) + ) + end + end + end + context 'when event is only streamed' do let(:context) do { name: name, author: author, scope: scope, target: target, created_at: 3.weeks.ago, stream_only: true } diff --git a/ee/spec/requests/ee/projects/deploy_tokens_controller_spec.rb b/ee/spec/requests/ee/projects/deploy_tokens_controller_spec.rb index 10431dd843188d0d67dc88fef61febcef704456b..7141a9018162d19b9a49e0bf3accab4b0715d3db 100644 --- a/ee/spec/requests/ee/projects/deploy_tokens_controller_spec.rb +++ b/ee/spec/requests/ee/projects/deploy_tokens_controller_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' RSpec.describe Projects::DeployTokensController do - let_it_be(:project) { create(:project) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } let_it_be(:user) { create(:user) } let_it_be(:deploy_token) { create(:deploy_token, projects: [project]) } let_it_be(:params) do @@ -32,6 +33,19 @@ MESSAGE expect(AuditEvent.last.details[:custom_message]).to eq(expected_message) + expect(AuditEvent.last.details).to include({ + custom_message: expected_message, + action: :custom + }) + end + + before do + stub_licensed_features(external_audit_events: true) + group.external_audit_event_destinations.create!(destination_url: 'http://example.com') + end + + it_behaves_like 'sends correct event type in audit event stream' do + let_it_be(:event_type) { "deploy_token_revoked" } end end end diff --git a/ee/spec/services/audit_events/build_service_spec.rb b/ee/spec/services/audit_events/build_service_spec.rb index a4c9085faf80021559aa5102e15a7cef80b7d90c..c3ff73da0e72e9553ce5596210fe76f9de6acee8 100644 --- a/ee/spec/services/audit_events/build_service_spec.rb +++ b/ee/spec/services/audit_events/build_service_spec.rb @@ -9,13 +9,15 @@ let(:target) { build_stubbed(:project) } let(:ip_address) { '192.168.8.8' } let(:message) { 'Added an interesting field from project Gotham' } + let(:additional_details) { { action: :custom } } subject(:service) do described_class.new( author: author, scope: scope, target: target, - message: message + message: message, + additional_details: additional_details ) end @@ -47,7 +49,8 @@ target_details: target.name, custom_message: message, ip_address: ip_address, - entity_path: scope.full_path + entity_path: scope.full_path, + action: :custom ) expect(event.ip_address).to eq(ip_address) @@ -122,7 +125,8 @@ target_id: target.id, target_type: target.class.name, target_details: target.name, - custom_message: message + custom_message: message, + action: :custom ) expect(event.ip_address).to be_nil diff --git a/ee/spec/services/ee/projects/deploy_tokens/create_service_spec.rb b/ee/spec/services/ee/projects/deploy_tokens/create_service_spec.rb index 8b0392947d4335864805eb4156b434b5ac2ec14a..a83aa215fb1346ac753b94d63774df29130076b7 100644 --- a/ee/spec/services/ee/projects/deploy_tokens/create_service_spec.rb +++ b/ee/spec/services/ee/projects/deploy_tokens/create_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' RSpec.describe Projects::DeployTokens::CreateService do - let_it_be(:entity) { create(:project) } + let_it_be(:group) { create :group } + let_it_be(:entity) { create(:project, group: group) } let_it_be(:user) { create(:user) } let(:deploy_token_params) { attributes_for(:deploy_token) } @@ -11,6 +12,11 @@ describe '#execute' do subject { described_class.new(entity, user, deploy_token_params).execute } + before do + stub_licensed_features(external_audit_events: true) + group.external_audit_event_destinations.create!(destination_url: 'http://example.com') + end + context 'when the deploy token is valid' do it 'creates an audit event' do expect { subject }.to change { AuditEvent.count }.by(1) @@ -20,7 +26,14 @@ with token_id: #{subject[:deploy_token].id} with scopes: #{subject[:deploy_token].scopes}. MESSAGE - expect(AuditEvent.last.details[:custom_message]).to eq(expected_message) + expect(AuditEvent.last.details).to include({ + custom_message: expected_message, + action: :custom + }) + end + + it_behaves_like 'sends correct event type in audit event stream' do + let_it_be(:event_type) { "deploy_token_created" } end end @@ -32,7 +45,14 @@ expected_message = "Attempted to create project deploy token but failed with message: Scopes can't be blank" - expect(AuditEvent.last.details[:custom_message]).to eq(expected_message) + expect(AuditEvent.last.details).to include({ + custom_message: expected_message, + action: :custom + }) + end + + it_behaves_like 'sends correct event type in audit event stream' do + let_it_be(:event_type) { "deploy_token_creation_failed" } end end end diff --git a/ee/spec/services/ee/projects/deploy_tokens/destroy_service_spec.rb b/ee/spec/services/ee/projects/deploy_tokens/destroy_service_spec.rb index b81e69bb2acaab8755ab95b19189fb71ac8aa9d4..a3e89bea526bb72df8f47793da9a998741bb7c04 100644 --- a/ee/spec/services/ee/projects/deploy_tokens/destroy_service_spec.rb +++ b/ee/spec/services/ee/projects/deploy_tokens/destroy_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' RSpec.describe Projects::DeployTokens::DestroyService do - let_it_be(:entity) { create(:project) } + let_it_be(:group) { create :group } + let_it_be(:entity) { create(:project, group: group) } let_it_be(:deploy_token) { create(:deploy_token, projects: [entity]) } let_it_be(:user) { create(:user) } let_it_be(:deploy_token_params) { { token_id: deploy_token.id } } @@ -24,7 +25,19 @@ with token_id: #{deploy_token.id} with scopes: #{deploy_token.scopes}. MESSAGE - expect(AuditEvent.last.details[:custom_message]).to eq(expected_message) + expect(AuditEvent.last.details).to include({ + custom_message: expected_message, + action: :custom + }) + end + + before do + stub_licensed_features(external_audit_events: true) + group.external_audit_event_destinations.create!(destination_url: 'http://example.com') + end + + it_behaves_like 'sends correct event type in audit event stream' do + let_it_be(:event_type) { "deploy_token_destroyed" } end end end diff --git a/ee/spec/support/shared_examples/audit/audit_event_type_stream_shared_examples.rb b/ee/spec/support/shared_examples/audit/audit_event_type_stream_shared_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..02dfbb25de3aac1cd8a287bbde3c6c07744ffc57 --- /dev/null +++ b/ee/spec/support/shared_examples/audit/audit_event_type_stream_shared_examples.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'sends correct event type in audit event stream' do + it 'sends correct event type in audit event stream' do + expect(AuditEvents::AuditEventStreamingWorker).to receive(:perform_async).with(event_type, + anything, + anything) + + subject + end +end