diff options
Diffstat (limited to 'app/models/ci/job_token')
-rw-r--r-- | app/models/ci/job_token/allowlist.rb | 9 | ||||
-rw-r--r-- | app/models/ci/job_token/project_scope_link.rb | 25 | ||||
-rw-r--r-- | app/models/ci/job_token/scope.rb | 65 |
3 files changed, 80 insertions, 19 deletions
diff --git a/app/models/ci/job_token/allowlist.rb b/app/models/ci/job_token/allowlist.rb index 9e9a0a68ebd..618dc2da05c 100644 --- a/app/models/ci/job_token/allowlist.rb +++ b/app/models/ci/job_token/allowlist.rb @@ -17,6 +17,15 @@ module Ci Project.from_union(target_projects, remove_duplicates: false) end + def add!(target_project, user:) + Ci::JobToken::ProjectScopeLink.create!( + source_project: @source_project, + direction: @direction, + target_project: target_project, + added_by: user + ) + end + private def source_links diff --git a/app/models/ci/job_token/project_scope_link.rb b/app/models/ci/job_token/project_scope_link.rb index b784f93651a..96e370bba1e 100644 --- a/app/models/ci/job_token/project_scope_link.rb +++ b/app/models/ci/job_token/project_scope_link.rb @@ -1,24 +1,31 @@ # frozen_string_literal: true -# The connection between a source project (which defines the job token scope) -# and a target project which is the one allowed to be accessed by the job token. +# The connection between a source project (which the job token scope's allowlist applies too) +# and a target project which is added to the scope's allowlist. module Ci module JobToken class ProjectScopeLink < Ci::ApplicationRecord self.table_name = 'ci_job_token_project_scope_links' + PROJECT_LINK_DIRECTIONAL_LIMIT = 100 + belongs_to :source_project, class_name: 'Project' + # the project added to the scope's allowlist belongs_to :target_project, class_name: 'Project' belongs_to :added_by, class_name: 'User' - scope :with_source, ->(project) { where(source_project: project) } - scope :with_target, ->(project) { where(target_project: project) } + scope :with_access_direction, ->(direction) { where(direction: direction) } + scope :with_source, ->(project) { where(source_project: project) } + scope :with_target, ->(project) { where(target_project: project) } validates :source_project, presence: true validates :target_project, presence: true validate :not_self_referential_link + validate :source_project_under_link_limit, on: :create + # When outbound the target project is allowed to be accessed by the source job token. + # When inbound the source project is allowed to be accessed by the target job token. enum direction: { outbound: 0, inbound: 1 @@ -37,6 +44,16 @@ module Ci self.errors.add(:target_project, _("can't be the same as the source project")) end end + + def source_project_under_link_limit + return unless source_project + + existing_links_count = self.class.with_source(source_project).with_access_direction(direction).count + + if existing_links_count >= PROJECT_LINK_DIRECTIONAL_LIMIT + errors.add(:source_project, "exceeds the allowable number of project links in this direction") + end + end end end end diff --git a/app/models/ci/job_token/scope.rb b/app/models/ci/job_token/scope.rb index e320c0f92d1..20775077bd8 100644 --- a/app/models/ci/job_token/scope.rb +++ b/app/models/ci/job_token/scope.rb @@ -2,18 +2,17 @@ # This model represents the scope of access for a CI_JOB_TOKEN. # -# A scope is initialized with a project. +# A scope is initialized with a current project. # # Projects can be added to the scope by adding ScopeLinks to # create an allowlist of projects in either access direction (inbound, outbound). # -# Currently, projects in the outbound allowlist can be accessed via the token -# in the source project. +# Projects in the outbound allowlist can be accessed via the current project's job token. # -# TODO(Issue #346298) Projects in the inbound allowlist can use their token to access -# the source project. +# Projects in the inbound allowlist can use their project's job token to +# access the current project. # -# CI_JOB_TOKEN should be considered untrusted without these features enabled. +# CI_JOB_TOKEN should be considered untrusted without a scope enabled. # module Ci @@ -25,34 +24,70 @@ module Ci @current_project = current_project end - def allows?(accessed_project) - self_referential?(accessed_project) || outbound_allows?(accessed_project) + def accessible?(accessed_project) + self_referential?(accessed_project) || ( + outbound_accessible?(accessed_project) && + inbound_accessible?(accessed_project) + ) end def outbound_projects outbound_allowlist.projects end - # Deprecated: use outbound_projects, TODO(Issue #346298) remove references to all_project - def all_projects - outbound_projects + def inbound_projects + inbound_allowlist.projects + end + + def add!(added_project, user:, direction:) + case direction + when :inbound + inbound_allowlist.add!(added_project, user: user) + when :outbound + outbound_allowlist.add!(added_project, user: user) + end end private - def outbound_allows?(accessed_project) + def outbound_accessible?(accessed_project) # if the setting is disabled any project is considered to be in scope. - return true unless @current_project.ci_outbound_job_token_scope_enabled? + return true unless current_project.ci_outbound_job_token_scope_enabled? outbound_allowlist.includes?(accessed_project) end + def inbound_accessible?(accessed_project) + # if the flag or setting is disabled any project is considered to be in scope. + return true unless Feature.enabled?(:ci_inbound_job_token_scope, accessed_project) + return true unless accessed_project.ci_inbound_job_token_scope_enabled? + + inbound_linked_as_accessible?(accessed_project) + end + + # We don't check the inbound allowlist here. That is because + # the access check starts from the current project but the inbound + # allowlist contains projects that can access the current project. + def inbound_linked_as_accessible?(accessed_project) + inbound_accessible_projects(accessed_project).includes?(current_project) + end + + def inbound_accessible_projects(accessed_project) + Ci::JobToken::Allowlist.new(accessed_project, direction: :inbound) + end + + # User created list of projects allowed to access the current project + def inbound_allowlist + Ci::JobToken::Allowlist.new(current_project, direction: :inbound) + end + + # User created list of projects that can be accessed from the current project def outbound_allowlist - Ci::JobToken::Allowlist.new(@current_project, direction: :outbound) + Ci::JobToken::Allowlist.new(current_project, direction: :outbound) end def self_referential?(accessed_project) - @current_project.id == accessed_project.id + current_project.id == accessed_project.id end end end |