Commit aa9612f1 authored by Pedro Pombeiro's avatar Pedro Pombeiro

Log CI runner unregistration audit events

Changelog: added
EE: true
parent 1cdc91a9
...@@ -34,7 +34,7 @@ class Admin::RunnersController < Admin::ApplicationController ...@@ -34,7 +34,7 @@ class Admin::RunnersController < Admin::ApplicationController
end end
def destroy def destroy
Ci::UnregisterRunnerService.new(@runner).execute Ci::UnregisterRunnerService.new(@runner, current_user).execute
redirect_to admin_runners_path, status: :found redirect_to admin_runners_path, status: :found
end end
......
...@@ -35,7 +35,7 @@ class Groups::RunnersController < Groups::ApplicationController ...@@ -35,7 +35,7 @@ class Groups::RunnersController < Groups::ApplicationController
if @runner.belongs_to_more_than_one_project? if @runner.belongs_to_more_than_one_project?
redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found, alert: _('Runner was not deleted because it is assigned to multiple projects.') redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found, alert: _('Runner was not deleted because it is assigned to multiple projects.')
else else
Ci::UnregisterRunnerService.new(@runner).execute Ci::UnregisterRunnerService.new(@runner, current_user).execute
redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), status: :found
end end
......
...@@ -23,7 +23,7 @@ class Projects::RunnersController < Projects::ApplicationController ...@@ -23,7 +23,7 @@ class Projects::RunnersController < Projects::ApplicationController
def destroy def destroy
if @runner.only_for?(project) if @runner.only_for?(project)
Ci::UnregisterRunnerService.new(@runner).execute Ci::UnregisterRunnerService.new(@runner, current_user).execute
end end
redirect_to project_runners_path(@project), status: :found redirect_to project_runners_path(@project), status: :found
......
...@@ -20,7 +20,7 @@ module Mutations ...@@ -20,7 +20,7 @@ module Mutations
error = authenticate_delete_runner!(runner) error = authenticate_delete_runner!(runner)
return { errors: [error] } if error return { errors: [error] } if error
::Ci::UnregisterRunnerService.new(runner).execute ::Ci::UnregisterRunnerService.new(runner, current_user).execute
{ errors: runner.errors.full_messages } { errors: runner.errors.full_messages }
end end
......
...@@ -2,11 +2,13 @@ ...@@ -2,11 +2,13 @@
module Ci module Ci
class UnregisterRunnerService class UnregisterRunnerService
attr_reader :runner attr_reader :runner, :author
# @param [Ci::Runner] runner the runner to unregister/destroy # @param [Ci::Runner] runner the runner to unregister/destroy
def initialize(runner) # @param [User, authentication token String] author the user or the authentication token that authorizes the removal
def initialize(runner, author)
@runner = runner @runner = runner
@author = author
end end
def execute def execute
...@@ -14,3 +16,5 @@ module Ci ...@@ -14,3 +16,5 @@ module Ci
end end
end end
end end
Ci::UnregisterRunnerService.prepend_mod
# frozen_string_literal: true
module AuditEvents
class UnregisterRunnerAuditEventService < RunnerRegistrationAuditEventService
def token_field
:runner_authentication_token
end
def message
"Unregistered #{runner_type} CI runner"
end
end
end
# frozen_string_literal: true
module EE
module Ci
# Unregisters a CI Runner and logs an audit event
#
module UnregisterRunnerService
extend ::Gitlab::Utils::Override
include ::Audit::Changes
override :execute
def execute
scopes = runner_scopes # Save the scopes before destroying the record
result = super
audit_log_event(scopes)
result
end
private
def runner_scopes
case runner.runner_type
when 'instance_type'
[nil]
when 'group_type'
runner.groups.to_a
when 'project_type'
runner.projects.to_a
end
end
def audit_log_event(scopes)
scopes.each do |scope|
::AuditEvents::UnregisterRunnerAuditEventService.new(runner, author, scope)
.track_event
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AuditEvents::UnregisterRunnerAuditEventService do
let_it_be(:user) { create(:user) }
let(:service) { described_class.new(runner, author, entity) }
let(:common_attrs) do
{
author_id: -1,
created_at: timestamp,
id: subject.id,
target_type: runner.class.name,
target_id: runner.id,
ip_address: nil,
details: {
target_type: runner.class.name,
target_id: runner.id,
ip_address: nil
}
}
end
shared_examples 'expected audit log' do
it 'returns audit event attributes' do
travel_to(timestamp) do
expect(subject.attributes).to eq(attrs.stringify_keys)
end
end
end
shared_context 'when unregistering runner' do
let(:extra_attrs) { {} }
let(:attrs) do
entity_class_name = entity.class.name if entity
common_attrs.deep_merge(
entity_id: entity&.id || -1,
entity_type: entity ? entity_class_name : 'User',
entity_path: entity&.full_path,
target_details: target_details,
details: {
custom_message: "Unregistered #{entity_class_name&.downcase || 'instance'} CI runner",
entity_path: entity&.full_path,
target_details: target_details
}
).deep_merge(extra_attrs)
end
context 'with authentication token author' do
let(:author) { 'b6bce79c3a' }
let(:extra_attrs) do
{
author_name: author[0...8],
details: {
author_name: author[0...8],
runner_authentication_token: author[0...8]
}
}
end
it_behaves_like 'expected audit log'
end
context 'with User author' do
let(:author) { user }
let(:extra_attrs) do
{
author_id: author.id,
author_name: author.name,
details: { author_name: author.name }
}
end
it_behaves_like 'expected audit log'
end
end
describe '#track_event' do
before do
stub_licensed_features(admin_audit_log: true)
end
subject { service.track_event }
let(:timestamp) { Time.zone.local(2021, 12, 28) }
context 'for instance runner' do
before do
stub_licensed_features(extended_audit_events: true, admin_audit_log: true)
end
let_it_be(:runner) { create(:ci_runner) }
let(:entity) { }
let(:extra_attrs) { {} }
let(:target_details) { ::Gitlab::Routing.url_helpers.admin_runner_path(runner) }
let(:attrs) do
common_attrs.deep_merge(
author_name: nil,
entity_id: -1,
entity_type: 'User',
entity_path: nil,
target_details: target_details,
details: {
custom_message: 'Unregistered instance CI runner',
entity_path: nil,
target_details: target_details
}
).deep_merge(extra_attrs)
end
context 'with authentication token author' do
let(:author) { 'b6bce79c3a' }
let(:extra_attrs) do
{
details: { runner_authentication_token: author[0...8] }
}
end
it_behaves_like 'expected audit log'
end
context 'with User author' do
let(:author) { user }
let(:extra_attrs) do
{ author_id: author.id }
end
it_behaves_like 'expected audit log'
end
end
context 'for group runner' do
let_it_be(:entity) { create(:group) }
let_it_be(:runner) { create(:ci_runner, :group, groups: [entity]) }
include_context 'when unregistering runner' do
let(:extra_attrs) { {} }
let(:target_details) { ::Gitlab::Routing.url_helpers.group_runner_path(entity, runner) }
end
end
context 'for project runner' do
let_it_be(:entity) { create(:project) }
let_it_be(:runner) { create(:ci_runner, :project, projects: [entity]) }
include_context 'when unregistering runner' do
let(:extra_attrs) { {} }
let(:target_details) { ::Gitlab::Routing.url_helpers.project_runner_path(entity, runner) }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Ci::UnregisterRunnerService, '#execute' do
let(:audit_service) { instance_double(::AuditEvents::UnregisterRunnerAuditEventService) }
let(:current_user) { nil }
let(:token) { 'abc123' }
subject { described_class.new(runner, current_user || token).execute }
context 'on an instance runner' do
let(:runner) { create(:ci_runner) }
it 'logs an audit event with the instance scope' do
expect(audit_service).to receive(:track_event).once.and_return('track_event_return_value')
expect(::AuditEvents::UnregisterRunnerAuditEventService).to receive(:new)
.with(runner, token, nil)
.once.and_return(audit_service)
subject
end
end
context 'on a group runner' do
let(:group) { create(:group) }
let(:runner) { create(:ci_runner, :group, groups: [group]) }
let(:current_user) { build(:user) }
it 'logs an audit event with the group scope' do
expect(audit_service).to receive(:track_event).once.and_return('track_event_return_value')
expect(::AuditEvents::UnregisterRunnerAuditEventService).to receive(:new)
.with(runner, current_user, group)
.once.and_return(audit_service)
subject
end
end
context 'on a project runner' do
let(:project1) { create(:project) }
let(:project2) { create(:project) }
let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
it 'logs an audit event per project' do
expect(audit_service).to receive(:track_event).twice.and_return('track_event_return_value')
expect(::AuditEvents::UnregisterRunnerAuditEventService).to receive(:new)
.with(runner, token, project1)
.once.and_return(audit_service)
expect(::AuditEvents::UnregisterRunnerAuditEventService).to receive(:new)
.with(runner, token, project2)
.once.and_return(audit_service)
subject
end
end
end
...@@ -57,7 +57,7 @@ module API ...@@ -57,7 +57,7 @@ module API
delete '/', feature_category: :runner do delete '/', feature_category: :runner do
authenticate_runner! authenticate_runner!
destroy_conditionally!(current_runner) { ::Ci::UnregisterRunnerService.new(current_runner).execute } destroy_conditionally!(current_runner) { ::Ci::UnregisterRunnerService.new(current_runner, params[:token]).execute }
end end
desc 'Validates authentication credentials' do desc 'Validates authentication credentials' do
......
...@@ -110,7 +110,7 @@ module API ...@@ -110,7 +110,7 @@ module API
authenticate_delete_runner!(runner) authenticate_delete_runner!(runner)
destroy_conditionally!(runner) { ::Ci::UnregisterRunnerService.new(runner).execute } destroy_conditionally!(runner) { ::Ci::UnregisterRunnerService.new(runner, current_user).execute }
end end
desc 'List jobs running on a runner' do desc 'List jobs running on a runner' do
......
...@@ -105,7 +105,7 @@ RSpec.describe Admin::RunnersController do ...@@ -105,7 +105,7 @@ RSpec.describe Admin::RunnersController do
describe '#destroy' do describe '#destroy' do
it 'destroys the runner' do it 'destroys the runner' do
expect_next_instance_of(Ci::UnregisterRunnerService, runner) do |service| expect_next_instance_of(Ci::UnregisterRunnerService, runner, user) do |service|
expect(service).to receive(:execute).once.and_call_original expect(service).to receive(:execute).once.and_call_original
end end
......
...@@ -190,7 +190,7 @@ RSpec.describe Groups::RunnersController do ...@@ -190,7 +190,7 @@ RSpec.describe Groups::RunnersController do
end end
it 'destroys the runner and redirects' do it 'destroys the runner and redirects' do
expect_next_instance_of(Ci::UnregisterRunnerService, runner) do |service| expect_next_instance_of(Ci::UnregisterRunnerService, runner, user) do |service|
expect(service).to receive(:execute).once.and_call_original expect(service).to receive(:execute).once.and_call_original
end end
......
...@@ -37,7 +37,7 @@ RSpec.describe Projects::RunnersController do ...@@ -37,7 +37,7 @@ RSpec.describe Projects::RunnersController do
describe '#destroy' do describe '#destroy' do
it 'destroys the runner' do it 'destroys the runner' do
expect_next_instance_of(Ci::UnregisterRunnerService, runner) do |service| expect_next_instance_of(Ci::UnregisterRunnerService, runner, user) do |service|
expect(service).to receive(:execute).once.and_call_original expect(service).to receive(:execute).once.and_call_original
end end
......
...@@ -55,7 +55,7 @@ RSpec.describe Mutations::Ci::Runner::Delete do ...@@ -55,7 +55,7 @@ RSpec.describe Mutations::Ci::Runner::Delete do
it 'deletes runner' do it 'deletes runner' do
mutation_params[:id] = project_runner.to_global_id mutation_params[:id] = project_runner.to_global_id
expect_next_instance_of(::Ci::UnregisterRunnerService, project_runner) do |service| expect_next_instance_of(::Ci::UnregisterRunnerService, project_runner, current_ctx[:current_user]) do |service|
expect(service).to receive(:execute).once.and_call_original expect(service).to receive(:execute).once.and_call_original
end end
...@@ -76,7 +76,7 @@ RSpec.describe Mutations::Ci::Runner::Delete do ...@@ -76,7 +76,7 @@ RSpec.describe Mutations::Ci::Runner::Delete do
mutation_params[:id] = two_projects_runner.to_global_id mutation_params[:id] = two_projects_runner.to_global_id
allow_next_instance_of(::Ci::UnregisterRunnerService) do |service| allow_next_instance_of(::Ci::UnregisterRunnerService) do |service|
expect(service).not_to receive(:execute).once expect(service).not_to receive(:execute)
end end
expect { subject }.not_to change { Ci::Runner.count } expect { subject }.not_to change { Ci::Runner.count }
expect(subject[:errors]).to contain_exactly("Runner #{two_projects_runner.to_global_id} associated with more than one project") expect(subject[:errors]).to contain_exactly("Runner #{two_projects_runner.to_global_id} associated with more than one project")
...@@ -89,7 +89,7 @@ RSpec.describe Mutations::Ci::Runner::Delete do ...@@ -89,7 +89,7 @@ RSpec.describe Mutations::Ci::Runner::Delete do
let(:current_ctx) { { current_user: admin_user } } let(:current_ctx) { { current_user: admin_user } }
it 'deletes runner' do it 'deletes runner' do
expect_next_instance_of(::Ci::UnregisterRunnerService, runner) do |service| expect_next_instance_of(::Ci::UnregisterRunnerService, runner, current_ctx[:current_user]) do |service|
expect(service).to receive(:execute).once.and_call_original expect(service).to receive(:execute).once.and_call_original
end end
......
...@@ -530,7 +530,7 @@ RSpec.describe API::Ci::Runners do ...@@ -530,7 +530,7 @@ RSpec.describe API::Ci::Runners do
context 'admin user' do context 'admin user' do
context 'when runner is shared' do context 'when runner is shared' do
it 'deletes runner' do it 'deletes runner' do
expect_next_instance_of(Ci::UnregisterRunnerService, shared_runner) do |service| expect_next_instance_of(Ci::UnregisterRunnerService, shared_runner, admin) do |service|
expect(service).to receive(:execute).once.and_call_original expect(service).to receive(:execute).once.and_call_original
end end
...@@ -548,7 +548,7 @@ RSpec.describe API::Ci::Runners do ...@@ -548,7 +548,7 @@ RSpec.describe API::Ci::Runners do
context 'when runner is not shared' do context 'when runner is not shared' do
it 'deletes used project runner' do it 'deletes used project runner' do
expect_next_instance_of(Ci::UnregisterRunnerService, project_runner) do |service| expect_next_instance_of(Ci::UnregisterRunnerService, project_runner, admin) do |service|
expect(service).to receive(:execute).once.and_call_original expect(service).to receive(:execute).once.and_call_original
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ::Ci::UnregisterRunnerService, '#execute' do RSpec.describe ::Ci::UnregisterRunnerService, '#execute' do
subject { described_class.new(runner).execute } subject { described_class.new(runner, 'some_token').execute }
let(:runner) { create(:ci_runner) } let(:runner) { create(:ci_runner) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment