Commit f1ecc1b7 authored by Serena Fang's avatar Serena Fang Committed by Robert Speicher

Audit events for project access tokens

Will need to move audit event service call to ee
parent 50fb21a5
......@@ -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