File: authorization.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (67 lines) | stat: -rw-r--r-- 3,016 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# frozen_string_literal: true

# In this model we log all authorizations from CI_JOB_TOKEN where the scope is disabled.
# We log the source project (where the token comes from) and the target project being
# accessed.
# The purpose of this model is to assist project owners in enabling the job token scope
# and making informed decisions on what to add to the allowlist.

module Ci
  module JobToken
    class Authorization < Ci::ApplicationRecord
      self.table_name = 'ci_job_token_authorizations'

      belongs_to :origin_project, class_name: 'Project' # where the job token came from
      belongs_to :accessed_project, class_name: 'Project' # what project the job token accessed

      REQUEST_CACHE_KEY = :job_token_authorizations
      CAPTURE_DELAY = 5.minutes

      scope :for_project, ->(accessed_project) { where(accessed_project: accessed_project) }
      scope :preload_origin_project, -> { includes(origin_project: :route) }

      # Record in SafeRequestStore a cross-project access attempt
      def self.capture(origin_project:, accessed_project:)
        # Skip self-referential accesses as they are always allowed and don't need
        # to be logged neither added to the allowlist.
        return if origin_project == accessed_project

        # We are tracking an attempt of cross-project utilization but we
        # are not yet persisting this log until a request successfully
        # completes. We will do that in a middleware. This is because the policy
        # rule about job token scope may be satisfied but a subsequent rule in
        # the Declarative Policies may block the authorization.
        Gitlab::SafeRequestStore.fetch(REQUEST_CACHE_KEY) do
          { accessed_project_id: accessed_project.id, origin_project_id: origin_project.id }
        end
      end

      # Schedule logging of captured authorizations in a background worker.
      # We add a 5 minutes delay with deduplication logic so that we log the same authorization
      # at most every 5 minutes. Otherwise, in high traffic projects we could be logging
      # authorizations very frequently.
      def self.log_captures_async
        authorizations = captured_authorizations
        return unless authorizations

        accessed_project_id = authorizations[:accessed_project_id]
        Ci::JobToken::LogAuthorizationWorker # rubocop:disable CodeReuse/Worker -- This method is called from a middleware and it's better tested
          .perform_in(CAPTURE_DELAY, accessed_project_id, authorizations[:origin_project_id])
      end

      def self.log_captures!(accessed_project_id:, origin_project_id:)
        upsert({
          accessed_project_id: accessed_project_id,
          origin_project_id: origin_project_id,
          last_authorized_at: Time.current
        },
          unique_by: [:accessed_project_id, :origin_project_id],
          on_duplicate: :update)
      end

      def self.captured_authorizations
        Gitlab::SafeRequestStore[REQUEST_CACHE_KEY]
      end
    end
  end
end