Commit ec17dd6e authored by Tan Le's avatar Tan Le

Use IP address from request on audit events

IP address captured in `current_sign_in_ip` does not refresh if the user
re-uses an existing session. It is the last IP used to sign in. IP
address from `request` object is fresher and should be use as default.

IP address in details hash needs to be extracted in the initializer to
avoid being overridden by the chaining building method (i.e. for_).
parent 3064cb31
...@@ -16,6 +16,7 @@ class AuditEventService ...@@ -16,6 +16,7 @@ class AuditEventService
@author = build_author(author) @author = build_author(author)
@entity = entity @entity = entity
@details = details @details = details
@ip_address = (@details[:ip_address].presence || @author.current_sign_in_ip)
end end
# Builds the @details attribute for authentication # Builds the @details attribute for authentication
...@@ -49,6 +50,8 @@ class AuditEventService ...@@ -49,6 +50,8 @@ class AuditEventService
private private
attr_reader :ip_address
def build_author(author) def build_author(author)
case author case author
when User when User
......
...@@ -84,14 +84,12 @@ module EE ...@@ -84,14 +84,12 @@ module EE
# #
# @return [AuditEventService] # @return [AuditEventService]
def for_failed_login def for_failed_login
ip = @details[:ip_address]
auth = @details[:with] || 'STANDARD' auth = @details[:with] || 'STANDARD'
@details = { @details = {
failed_login: auth.upcase, failed_login: auth.upcase,
author_name: @author.name, author_name: @author.name,
target_details: @author.name, target_details: @author.name
ip_address: ip
} }
self self
...@@ -125,10 +123,8 @@ module EE ...@@ -125,10 +123,8 @@ module EE
end end
def prepare_security_event def prepare_security_event
if admin_audit_log_enabled? add_security_event_admin_details!
add_security_event_admin_details! add_impersonation_details!
add_impersonation_details!
end
end end
# Creates an event record in DB # Creates an event record in DB
...@@ -138,16 +134,18 @@ module EE ...@@ -138,16 +134,18 @@ module EE
def unauth_security_event def unauth_security_event
return unless audit_events_enabled? return unless audit_events_enabled?
@details.delete(:ip_address) unless admin_audit_log_enabled? add_security_event_admin_details!
@details[:entity_path] = @entity&.full_path if admin_audit_log_enabled?
SecurityEvent.create( payload = {
author_id: @author.id, author_id: @author.id,
entity_id: @entity.respond_to?(:id) ? @entity.id : -1, entity_id: @entity.respond_to?(:id) ? @entity.id : -1,
entity_type: 'User', entity_type: 'User',
details: @details, details: @details
ip_address: ip_address }
)
payload[:ip_address] = ip_address if admin_audit_log_enabled?
SecurityEvent.create(payload)
end end
# Builds the @details attribute for user # Builds the @details attribute for user
...@@ -248,26 +246,25 @@ module EE ...@@ -248,26 +246,25 @@ module EE
author_name: @author&.name, author_name: @author&.name,
target_id: key_title, target_id: key_title,
target_type: model_class, target_type: model_class,
target_details: key_title, target_details: key_title
ip_address: @details[:ip_address]
} }
end end
self self
end end
def ip_address
@author.current_sign_in_ip || @details[:ip_address]
end
def add_security_event_admin_details! def add_security_event_admin_details!
return unless admin_audit_log_enabled?
@details.merge!( @details.merge!(
ip_address: ip_address, ip_address: ip_address,
entity_path: @entity.full_path entity_path: @entity&.full_path
) )
end end
def add_impersonation_details! def add_impersonation_details!
return unless admin_audit_log_enabled?
if @author.is_a?(::Gitlab::Audit::ImpersonatedAuthor) if @author.is_a?(::Gitlab::Audit::ImpersonatedAuthor)
@details.merge!(impersonated_by: @author.impersonated_by) @details.merge!(impersonated_by: @author.impersonated_by)
end end
......
---
title: Use IP address from request on audit events
merge_request: 35834
author:
type: changed
...@@ -6,8 +6,9 @@ RSpec.describe AuditEventService do ...@@ -6,8 +6,9 @@ RSpec.describe AuditEventService do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { create(:user, current_sign_in_ip: '192.168.68.104') } let(:user) { create(:user, current_sign_in_ip: '192.168.68.104') }
let(:project_member) { create(:project_member, user: user, expires_at: 1.day.from_now) } let(:project_member) { create(:project_member, user: user, expires_at: 1.day.from_now) }
let(:request_ip_address) { '127.0.0.1' }
let(:details) { { action: :destroy } } let(:details) { { action: :destroy, ip_address: request_ip_address } }
let(:service) { described_class.new(user, project, details) } let(:service) { described_class.new(user, project, details) }
describe '#for_member' do describe '#for_member' do
...@@ -52,48 +53,6 @@ RSpec.describe AuditEventService do ...@@ -52,48 +53,6 @@ RSpec.describe AuditEventService do
expect(event[:details][:expiry_to]).to eq(1.day.from_now.to_date) expect(event[:details][:expiry_to]).to eq(1.day.from_now.to_date)
end end
end end
context 'admin audit log licensed' do
before do
stub_licensed_features(admin_audit_log: true)
end
it 'has the entity full path' do
event = service.for_member(project_member).security_event
expect(event[:details][:entity_path]).to eq(project.full_path)
end
it 'has the IP address in the details hash' do
event = service.for_member(project_member).security_event
expect(event[:details][:ip_address]).to eq(user.current_sign_in_ip)
end
it 'has the IP address stored in a separate attribute' do
event = service.for_member(project_member).security_event
expect(event.ip_address).to eq(user.current_sign_in_ip)
end
end
context 'admin audit log unlicensed' do
before do
stub_licensed_features(admin_audit_log: false)
end
it 'does not have the entity full path' do
event = service.for_member(project_member).security_event
expect(event[:details]).not_to have_key(:entity_path)
end
it 'does not have the ip_address' do
event = service.for_member(project_member).security_event
expect(event[:details]).not_to have_key(:ip_address)
end
end
end end
describe '#security_event' do describe '#security_event' do
...@@ -138,34 +97,37 @@ RSpec.describe AuditEventService do ...@@ -138,34 +97,37 @@ RSpec.describe AuditEventService do
event = service.security_event event = service.security_event
expect(event.ip_address).to eq('10.11.12.13') expect(event.ip_address).to eq('10.11.12.13')
expect(event[:details][:ip_address]).to eq('10.11.12.13') expect(event.details[:ip_address]).to eq('10.11.12.13')
end end
end end
context 'for an authenticated user' do context 'for an authenticated user' do
let(:details) { {} }
it 'has the user IP address' do it 'has the user IP address' do
event = service.security_event event = service.security_event
expect(event.ip_address).to eq(user.current_sign_in_ip) expect(event.ip_address).to eq(user.current_sign_in_ip)
expect(event[:details][:ip_address]).to eq(user.current_sign_in_ip) expect(event.details[:ip_address]).to eq(user.current_sign_in_ip)
end end
end end
context 'for an impersonated user' do context 'for an impersonated user' do
let(:details) { {} }
let(:impersonator) { build(:user, name: 'Donald Duck', current_sign_in_ip: '192.168.88.88') } let(:impersonator) { build(:user, name: 'Donald Duck', current_sign_in_ip: '192.168.88.88') }
let(:user) { build(:user, impersonator: impersonator) } let(:user) { build(:user, impersonator: impersonator) }
it 'has the impersonator IP address' do it 'has the impersonator IP address' do
event = service.security_event event = service.security_event
expect(event[:details][:ip_address]).to eq('192.168.88.88') expect(event.details[:ip_address]).to eq('192.168.88.88')
expect(event.ip_address).to eq('192.168.88.88') expect(event.ip_address).to eq('192.168.88.88')
end end
it 'has the impersonator name' do it 'has the impersonator name' do
event = service.security_event event = service.security_event
expect(event[:details][:impersonated_by]).to eq('Donald Duck') expect(event.details[:impersonated_by]).to eq('Donald Duck')
end end
end end
end end
...@@ -259,8 +221,7 @@ RSpec.describe AuditEventService do ...@@ -259,8 +221,7 @@ RSpec.describe AuditEventService do
describe '#for_failed_login' do describe '#for_failed_login' do
let(:author_name) { 'testuser' } let(:author_name) { 'testuser' }
let(:ip_address) { '127.0.0.1' } let(:service) { described_class.new(author_name, nil, ip_address: request_ip_address) }
let(:service) { described_class.new(author_name, nil, ip_address: ip_address) }
let(:event) { service.for_failed_login.unauth_security_event } let(:event) { service.for_failed_login.unauth_security_event }
before do before do
...@@ -280,7 +241,7 @@ RSpec.describe AuditEventService do ...@@ -280,7 +241,7 @@ RSpec.describe AuditEventService do
end end
it 'has the right auth method for OAUTH' do it 'has the right auth method for OAUTH' do
oauth_service = described_class.new(author_name, nil, ip_address: ip_address, with: 'ldap') oauth_service = described_class.new(author_name, nil, ip_address: request_ip_address, with: 'ldap')
event = oauth_service.for_failed_login.unauth_security_event event = oauth_service.for_failed_login.unauth_security_event
expect(event.details[:failed_login]).to eq('LDAP') expect(event.details[:failed_login]).to eq('LDAP')
...@@ -292,8 +253,8 @@ RSpec.describe AuditEventService do ...@@ -292,8 +253,8 @@ RSpec.describe AuditEventService do
end end
it 'has the right IP address' do it 'has the right IP address' do
expect(event.ip_address).to eq(ip_address) expect(event.ip_address).to eq(request_ip_address)
expect(event.details[:ip_address]).to eq(ip_address) expect(event.details[:ip_address]).to eq(request_ip_address)
end end
end end
...@@ -315,8 +276,7 @@ RSpec.describe AuditEventService do ...@@ -315,8 +276,7 @@ RSpec.describe AuditEventService do
let(:target_user_full_path) { 'ejohn' } let(:target_user_full_path) { 'ejohn' }
let(:user) { instance_spy(User, full_path: target_user_full_path) } let(:user) { instance_spy(User, full_path: target_user_full_path) }
let(:custom_message) { 'Some strange event has occurred' } let(:custom_message) { 'Some strange event has occurred' }
let(:ip_address) { '127.0.0.1' } let(:options) { { action: action, custom_message: custom_message } }
let(:options) { { action: action, custom_message: custom_message, ip_address: ip_address } }
subject(:service) { described_class.new(current_user, user, options).for_user } subject(:service) { described_class.new(current_user, user, options).for_user }
...@@ -357,8 +317,7 @@ RSpec.describe AuditEventService do ...@@ -357,8 +317,7 @@ RSpec.describe AuditEventService do
author_name: author_name, author_name: author_name,
target_id: target_user_full_path, target_id: target_user_full_path,
target_type: 'User', target_type: 'User',
target_details: target_user_full_path, target_details: target_user_full_path
ip_address: ip_address
) )
end end
end end
...@@ -405,8 +364,28 @@ RSpec.describe AuditEventService do ...@@ -405,8 +364,28 @@ RSpec.describe AuditEventService do
expect(event.details[:entity_path]).to eq(project.full_path) expect(event.details[:entity_path]).to eq(project.full_path)
end end
it 'has the user IP address' do context 'request IP address is provided' do
expect(event.details[:ip_address]).to eq(user.current_sign_in_ip) let(:details) { { action: :destroy, ip_address: request_ip_address } }
it 'has the IP address in the details hash' do
expect(event.details[:ip_address]).to eq(request_ip_address)
end
it 'has the IP address stored in a separate attribute' do
expect(event.ip_address).to eq(request_ip_address)
end
end
context 'request IP address is not provided' do
let(:details) { { action: :destroy } }
it 'has the IP address in the details hash' do
expect(event.details[:ip_address]).to eq(user.current_sign_in_ip)
end
it 'has the IP address stored in a separate attribute' do
expect(event.ip_address).to eq(user.current_sign_in_ip)
end
end end
end end
......
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