Commit cf9847e5 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'project-access-token-audit-events' into 'master'

Audit events for project access tokens

See merge request gitlab-org/gitlab!51660
parents c729dc18 f1ecc1b7
......@@ -10,7 +10,7 @@ module ResourceAccessTokens
end
def execute
return error("User does not have permission to create #{resource_type} Access Token") unless has_permission_to_create?
return error("User does not have permission to create #{resource_type} access token") unless has_permission_to_create?
user = create_user
......@@ -26,6 +26,7 @@ module ResourceAccessTokens
token_response = create_personal_access_token(user)
if token_response.success?
log_event(token_response.payload[:personal_access_token])
success(token_response.payload[:personal_access_token])
else
delete_failed_user(user)
......@@ -105,6 +106,10 @@ module ResourceAccessTokens
resource.add_user(user, :maintainer, expires_at: params[:expires_at])
end
def log_event(token)
::Gitlab::AppLogger.info "PROJECT ACCESS TOKEN CREATION: created_by: #{current_user.username}, project_id: #{resource.id}, token_user: #{token.user.name}, token_id: #{token.id}"
end
def error(message)
ServiceResponse.error(message: message)
end
......@@ -114,3 +119,5 @@ module ResourceAccessTokens
end
end
end
ResourceAccessTokens::CreateService.prepend_if_ee('EE::ResourceAccessTokens::CreateService')
......@@ -21,6 +21,8 @@ module ResourceAccessTokens
destroy_bot_user
log_event
success("Access token #{access_token.name} has been revoked and the bot user has been scheduled for deletion.")
rescue StandardError => error
log_error("Failed to revoke access token for #{bot_user.name}: #{error.message}")
......@@ -57,6 +59,10 @@ module ResourceAccessTokens
end
end
def log_event
::Gitlab::AppLogger.info "PROJECT ACCESS TOKEN REVOCATION: revoked_by: #{current_user.username}, project_id: #{resource.id}, token_user: #{access_token.user.name}, token_id: #{access_token.id}"
end
def error(message)
ServiceResponse.error(message: message)
end
......@@ -66,3 +72,5 @@ module ResourceAccessTokens
end
end
end
ResourceAccessTokens::RevokeService.prepend_if_ee('EE::ResourceAccessTokens::RevokeService')
......@@ -100,6 +100,8 @@ From there, you can see the following actions:
- Added or removed users and groups from project approval groups ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213603) in GitLab 13.2)
- Project CI/CD variable added, removed, or protected status changed ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30857) in GitLab 13.4)
- User was approved via Admin Area ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276250) in GitLab 13.6)
- Project access token was successfully created or revoked ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230007) in GitLab 13.9)
- Failed attempt to create or revoke a project access token ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230007) in GitLab 13.9)
Project events can also be accessed via the [Project Audit Events API](../api/audit_events.md#project-audit-events).
......
# frozen_string_literal: true
module EE
module ResourceAccessTokens
module CreateService
def execute
super.tap do |response|
audit_event_service(response.payload[:access_token], response)
end
end
private
def audit_event_service(token, response)
message = if response.success?
"Created #{resource_type} access token with id: #{token.user.id} with scopes: #{token.scopes}"
else
"Attempted to create #{resource_type} access token but failed with message: #{response.message}"
end
::AuditEventService.new(
current_user,
resource,
target_details: token&.user&.name,
action: :custom,
custom_message: message,
ip_address: current_user.current_sign_in_ip
).security_event
end
end
end
end
# frozen_string_literal: true
module EE
module ResourceAccessTokens
module RevokeService
def execute
super.tap do |response|
audit_event_service(bot_user, response)
end
end
private
def audit_event_service(token, response)
message = if response.success?
"Revoked #{resource.class.name.downcase} access token with id: #{bot_user.id}"
else
"Attempted to revoke #{resource.class.name.downcase} access token with id: #{bot_user.id}, but failed with message: #{response.message}"
end
::AuditEventService.new(
current_user,
resource,
target_details: bot_user.name,
action: :custom,
custom_message: message,
ip_address: current_user.current_sign_in_ip
).security_event
end
end
end
end
---
title: Audit events for project access tokens
merge_request: 51660
author:
type: added
......@@ -29,6 +29,15 @@ RSpec.describe ResourceAccessTokens::CreateService do
end
end
shared_examples 'audit event details' do
it 'logs author and resource info', :aggregate_failures do
expect { subject }.to change { AuditEvent.count }.from(0).to(1)
expect(AuditEvent.last.author_id).to eq(user.id)
expect(AuditEvent.last.entity_id).to eq(resource.id)
expect(AuditEvent.last.ip_address).to eq(user.current_sign_in_ip)
end
end
describe '#execute' do
context 'with enforced group managed account enabled' do
let(:group) { create(:group_with_managed_accounts, :private) }
......@@ -54,5 +63,62 @@ RSpec.describe ResourceAccessTokens::CreateService do
it_behaves_like 'token creation succeeds'
end
context 'project access token audit events' do
let(:resource) { create(:project) }
context 'when project access token is successfully created' do
before do
resource.add_maintainer(user)
end
it_behaves_like 'audit event details'
it 'logs project access token details', :aggregate_failures do
response = subject
expect(AuditEvent.last.details[:custom_message]).to eq("Created project access token with id: #{response.payload[:access_token].user.id} with scopes: #{response.payload[:access_token].scopes}")
expect(AuditEvent.last.details[:target_details]).to match(response.payload[:access_token].user.name)
end
end
context 'when project access token is unsuccessfully created' do
context 'with inadequate permissions' do
before do
resource.add_developer(user)
end
it_behaves_like 'audit event details'
it 'logs the permission error message' do
subject
expect(AuditEvent.last.details[:custom_message]).to eq('Attempted to create project access token but failed with message: User does not have permission to create project access token')
end
end
context "when access provisioning fails" do
let_it_be(:user) { create(:user, :project_bot) }
let(:unpersisted_member) { build(:project_member, source: resource, user: user) }
before do
allow_next_instance_of(ResourceAccessTokens::CreateService) do |service|
allow(service).to receive(:create_user).and_return(user)
allow(service).to receive(:create_membership).and_return(unpersisted_member)
end
resource.add_maintainer(user)
end
it_behaves_like 'audit event details'
it 'logs the provisioning error message' do
subject
expect(AuditEvent.last.details[:custom_message]).to eq('Attempted to create project access token but failed with message: Could not provision maintainer access to project access token')
end
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ResourceAccessTokens::RevokeService do
subject { described_class.new(user, resource, access_token).execute }
let_it_be(:user) { create(:user) }
let_it_be(:resource_bot) { create(:user, :project_bot) }
let(:access_token) { create(:personal_access_token, user: resource_bot) }
shared_examples 'audit event details' do
it 'logs author and resource info', :aggregate_failures do
expect { subject }.to change { AuditEvent.count }.from(0).to(1)
expect(AuditEvent.last.author_id).to eq(user.id)
expect(AuditEvent.last.entity_id).to eq(resource.id)
expect(AuditEvent.last.ip_address).to eq(user.current_sign_in_ip)
end
end
context 'project access token audit events' do
let(:resource) { create(:project) }
context 'when project access token is successfully revoked' do
before do
resource.add_maintainer(user)
resource.add_maintainer(resource_bot)
end
it_behaves_like 'audit event details'
it 'logs project access token details', :aggregate_failures do
subject
expect(AuditEvent.last.details[:custom_message]).to match(/Revoked project access token with id: \d+/)
expect(AuditEvent.last.details[:target_details]).to eq(access_token.user.name)
end
end
context 'when project access token is unsuccessfully revoked' do
context 'when access token does not belong to this project' do
before do
resource.add_maintainer(user)
end
it_behaves_like 'audit event details'
it 'logs the find error message' do
subject
expect(AuditEvent.last.details[:custom_message]).to match(/Attempted to revoke project access token with id: \d+, but failed with message: Failed to find bot user/)
end
end
context 'with inadequate permissions' do
before do
resource.add_developer(user)
resource.add_maintainer(resource_bot)
end
it_behaves_like 'audit event details'
it 'logs the permission error message' do
subject
expect(AuditEvent.last.details[:custom_message]).to match(/Attempted to revoke project access token with id: \d+, but failed with message: #{user.name} cannot delete #{access_token.user.name}/)
end
end
end
end
end
......@@ -195,6 +195,14 @@ RSpec.describe ResourceAccessTokens::CreateService do
end
end
end
it 'logs the event' do
allow(Gitlab::AppLogger).to receive(:info)
response = subject
expect(Gitlab::AppLogger).to have_received(:info).with(/PROJECT ACCESS TOKEN CREATION: created_by: #{user.username}, project_id: #{resource.id}, token_user: #{response.payload[:access_token].user.name}, token_id: \d+/)
end
end
context 'when resource is a project' do
......@@ -208,7 +216,7 @@ RSpec.describe ResourceAccessTokens::CreateService do
response = subject
expect(response.error?).to be true
expect(response.errors).to include("User does not have permission to create #{resource_type} Access Token")
expect(response.errors).to include("User does not have permission to create #{resource_type} access token")
end
end
......
......@@ -40,6 +40,14 @@ RSpec.describe ResourceAccessTokens::RevokeService do
expect(User.exists?(resource_bot.id)).to be_falsy
end
it 'logs the event' do
allow(Gitlab::AppLogger).to receive(:info)
subject
expect(Gitlab::AppLogger).to have_received(:info).with("PROJECT ACCESS TOKEN REVOCATION: revoked_by: #{user.username}, project_id: #{resource.id}, token_user: #{resource_bot.name}, token_id: #{access_token.id}")
end
end
shared_examples 'rollback revoke steps' do
......
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