summaryrefslogtreecommitdiff
path: root/lib/gitlab/audit
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-09 15:11:31 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-09 15:11:31 +0000
commit283318c20561cc040b62397060771efa74db0d90 (patch)
tree31b724e53806352b1bff5e1e460e6f4445c4e0a0 /lib/gitlab/audit
parent1f229cdc22b5b32989bcff2037d8925c75703671 (diff)
downloadgitlab-ce-283318c20561cc040b62397060771efa74db0d90.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'lib/gitlab/audit')
-rw-r--r--lib/gitlab/audit/auditor.rb175
-rw-r--r--lib/gitlab/audit/null_target.rb19
-rw-r--r--lib/gitlab/audit/target.rb21
3 files changed, 215 insertions, 0 deletions
diff --git a/lib/gitlab/audit/auditor.rb b/lib/gitlab/audit/auditor.rb
new file mode 100644
index 00000000000..638977d24f1
--- /dev/null
+++ b/lib/gitlab/audit/auditor.rb
@@ -0,0 +1,175 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Audit
+ class Auditor
+ attr_reader :scope, :name
+
+ # Record audit events
+ #
+ # @param [Hash] context
+ # @option context [String] :name the operation name to be audited, used for error tracking
+ # @option context [User] :author the user who authors the change
+ # @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)
+ # i.e. multiple audit events
+ #
+ # audit_context = {
+ # name: 'merge_approval_rule_updated',
+ # author: current_user,
+ # scope: project_alpha,
+ # target: merge_approval_rule,
+ # message: 'a user has attempted to update an approval rule'
+ # }
+ #
+ # # in the initiating service
+ # Gitlab::Audit::Auditor.audit(audit_context) do
+ # service.execute
+ # end
+ #
+ # # in the model
+ # Auditable.push_audit_event('an approver has been added')
+ # Auditable.push_audit_event('an approval group has been removed')
+ #
+ # @example Using standard method call
+ # i.e. single audit event
+ #
+ # merge_approval_rule.save
+ # Gitlab::Audit::Auditor.audit(audit_context)
+ #
+ # @return result of block execution
+ def self.audit(context, &block)
+ auditor = new(context)
+
+ return unless auditor.audit_enabled?
+
+ if block
+ auditor.multiple_audit(&block)
+ else
+ auditor.single_audit
+ end
+ end
+
+ def initialize(context = {})
+ @context = context
+
+ @name = @context.fetch(:name, 'audit_operation')
+ @stream_only = @context.fetch(:stream_only, false)
+ @author = @context.fetch(:author)
+ @scope = @context.fetch(:scope)
+ @target = @context.fetch(:target)
+ @created_at = @context.fetch(:created_at, DateTime.current)
+ @message = @context.fetch(:message, '')
+ @additional_details = @context.fetch(:additional_details, {})
+ @ip_address = @context[:ip_address]
+ @target_details = @context[:target_details]
+ @authentication_event = @context.fetch(:authentication_event, false)
+ @authentication_provider = @context[:authentication_provider]
+ end
+
+ def single_audit
+ events = [build_event(@message)]
+
+ record(events)
+ end
+
+ def multiple_audit
+ # For now we dont have any need to implement multiple audit event functionality in CE
+ # Defined in EE
+ end
+
+ def record(events)
+ log_events(events) unless @stream_only
+ send_to_stream(events)
+ end
+
+ def log_events(events)
+ log_authentication_event
+ log_to_database(events)
+ log_to_file(events)
+ end
+
+ def audit_enabled?
+ authentication_event?
+ end
+
+ def authentication_event?
+ @authentication_event
+ end
+
+ def log_authentication_event
+ return unless Gitlab::Database.read_write? && authentication_event?
+
+ event = AuthenticationEvent.new(authentication_event_payload)
+ event.save!
+ rescue ActiveRecord::RecordInvalid => error
+ ::Gitlab::ErrorTracking.track_exception(error, audit_operation: @name)
+ end
+
+ def authentication_event_payload
+ {
+ # @author can be a User or various Gitlab::Audit authors.
+ # Only capture real users for successful authentication events.
+ user: author_if_user,
+ user_name: @author.name,
+ ip_address: @ip_address,
+ result: AuthenticationEvent.results[:success],
+ provider: @authentication_provider
+ }
+ end
+
+ def author_if_user
+ @author if @author.is_a?(User)
+ end
+
+ def send_to_stream(events)
+ # Defined in EE
+ end
+
+ def build_event(message)
+ AuditEvents::BuildService.new(
+ author: @author,
+ scope: @scope,
+ target: @target,
+ created_at: @created_at,
+ message: message,
+ additional_details: @additional_details,
+ ip_address: @ip_address,
+ target_details: @target_details
+ ).execute
+ end
+
+ def log_to_database(events)
+ AuditEvent.bulk_insert!(events)
+ rescue ActiveRecord::RecordInvalid => error
+ ::Gitlab::ErrorTracking.track_exception(error, audit_operation: @name)
+ end
+
+ def log_to_file(events)
+ file_logger = ::Gitlab::AuditJsonLogger.build
+
+ events.each { |event| file_logger.info(log_payload(event)) }
+ end
+
+ private
+
+ def log_payload(event)
+ payload = event.as_json
+ details = formatted_details(event.details)
+ payload["details"] = details
+ payload.merge!(details).as_json
+ end
+
+ def formatted_details(details)
+ details.merge(details.slice(:from, :to).transform_values(&:to_s))
+ end
+ end
+ end
+end
+
+Gitlab::Audit::Auditor.prepend_mod_with("Gitlab::Audit::Auditor")
diff --git a/lib/gitlab/audit/null_target.rb b/lib/gitlab/audit/null_target.rb
new file mode 100644
index 00000000000..ed3a50e9067
--- /dev/null
+++ b/lib/gitlab/audit/null_target.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Audit
+ class NullTarget
+ def id
+ nil
+ end
+
+ def type
+ nil
+ end
+
+ def details
+ nil
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/audit/target.rb b/lib/gitlab/audit/target.rb
new file mode 100644
index 00000000000..b9cb54aece8
--- /dev/null
+++ b/lib/gitlab/audit/target.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Audit
+ class Target
+ delegate :id, to: :@object
+
+ def initialize(object)
+ @object = object
+ end
+
+ def type
+ @object.class.name
+ end
+
+ def details
+ @object.try(:name) || @object.try(:audit_details) || 'unknown'
+ end
+ end
+ end
+end