Class: Ci::JobToken::Authorization

Inherits:
ApplicationRecord show all
Extended by:
Gitlab::InternalEventsTracking
Includes:
EachBatch
Defined in:
app/models/ci/job_token/authorization.rb

Constant Summary collapse

REQUEST_CACHE_KEY =
:job_token_authorizations
CAPTURE_DELAY =
5.minutes
AUTHORIZATION_ROW_LIMIT =
1000

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from HasCheckConstraints

HasCheckConstraints::NOT_NULL_CHECK_PATTERN

Constants included from ResetOnColumnErrors

ResetOnColumnErrors::MAX_RESET_PERIOD

Class Method Summary collapse

Methods included from Gitlab::InternalEventsTracking

track_internal_event

Methods inherited from ApplicationRecord

model_name, table_name_prefix

Methods inherited from ApplicationRecord

===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from Organizations::Sharding

#sharding_organization

Methods included from ResetOnColumnErrors

#reset_on_union_error, #reset_on_unknown_attribute_error

Methods included from Gitlab::SensitiveSerializableHash

#serializable_hash

Class Method Details

.add_to_request_store_hash(hash) ⇒ Object



100
101
102
103
# File 'app/models/ci/job_token/authorization.rb', line 100

def self.add_to_request_store_hash(hash)
  new_hash = captured_authorizations.present? ? captured_authorizations.merge(hash) : hash
  Gitlab::SafeRequestStore[REQUEST_CACHE_KEY] = new_hash
end

.capture(origin_project:, accessed_project:) ⇒ Object

Record in SafeRequestStore a cross-project access attempt



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'app/models/ci/job_token/authorization.rb', line 39

def self.capture(origin_project:, accessed_project:)
  label = origin_project == accessed_project ? 'same-project' : 'cross-project'
  track_internal_event(
    'authorize_job_token_with_disabled_scope',
    project: accessed_project,
    additional_properties: {
      label: label
    }
  )

  # We are tracking ci job token access to project resources, 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.
  add_to_request_store_hash(accessed_project_id: accessed_project.id, origin_project_id: origin_project.id)
end

.capture_job_token_policies(policies) ⇒ Object



57
58
59
# File 'app/models/ci/job_token/authorization.rb', line 57

def self.capture_job_token_policies(policies)
  add_to_request_store_hash(policies: policies)
end

.captured_authorizationsObject



105
106
107
# File 'app/models/ci/job_token/authorization.rb', line 105

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

.log_captures!(accessed_project_id:, origin_project_id:, policies: []) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'app/models/ci/job_token/authorization.rb', line 78

def self.log_captures!(accessed_project_id:, origin_project_id:, policies: [])
  current_time = Time.current
  attributes = {
    accessed_project_id: accessed_project_id,
    origin_project_id: origin_project_id,
    last_authorized_at: current_time
  }

  transaction do
    if policies.present?
      auth_log = lock.find_by(
        accessed_project_id: accessed_project_id,
        origin_project_id: origin_project_id
      )
      policies = policies.index_with(current_time)
      attributes[:job_token_policies] = auth_log ? auth_log.job_token_policies.merge(policies) : policies
    end

    upsert(attributes, unique_by: [:accessed_project_id, :origin_project_id], on_duplicate: :update)
  end
end

.log_captures_asyncObject

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.



65
66
67
68
69
70
71
72
73
74
75
76
# File 'app/models/ci/job_token/authorization.rb', line 65

def self.log_captures_async
  authorizations = captured_authorizations
  return unless authorizations

  accessed_project_id = authorizations[:accessed_project_id]
  origin_project_id = authorizations[:origin_project_id]
  return unless accessed_project_id && origin_project_id

  policies = authorizations.fetch(:policies, []).map(&:to_s)
  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, origin_project_id, policies)
end