Scope JobToken to authorized projects [RUN ALL RSPEC] [RUN AS-IF-FOSS]
What does this MR do?
Related to #329550 (closed)
Background
When a user authenticates using CI_JOB_TOKEN
should be considered untrusted because the user running the pipeline may not actually be the same user that triggered it. For example a scheduled pipeline always run in the context of the schedule owner even though the owner may not be the author of the commit the pipeline runs against.
Today, using CI_JOB_TOKEN
gives it complete permissions to all resources that the job.user
has access to. Instead we want to allow the CI_JOB_TOKEN
to be used only within a list of authorized projects.
Solution
With this MR we are introducing a security layer on top of CI_JOB_TOKEN
usage in the form of Ci::JobToken::Scope
. The scope defines the list of projects that the job token can access to. By default the scope for a given job token includes only the project where the job token originates from.
This MR introduces also a concept of ScopeLink
as a model that allows to indicate all projects that a given job token can access.
In a follow-up MR we will introduce operations that allow maintainers to add/remove projects to/from a scope, via project settings, as well as enabling this feature at project-level.
Example
Scenario: A CI_JOB_TOKEN, from a job in project A
, is used to trigger a multi-project pipeline on project B
.
Initially Project A
's scope is a collection containing only itself: [A]
, meaning that the token can only be used for operations within A
. Even if the user that runs the job that generated the CI_JOB_TOKEN
is a member of project B
. Project B
must be added to Project A
's scope for the token to access B
.
If the multi-project pipeline in B
wants to read some data from project C
, project C
must be present in B
's scope in project settings.
Feature flag
This change is introduced behind a feature flag ci_scoped_job_token
which is disabled by default. FF issue: #332272 (closed).
DB migrations
$ rails db:migrate:redo
== 20210527194558 CreateCiJobTokenProjectScopeLinks: reverting ================
-- drop_table(:ci_job_token_project_scope_links, {:if_exists=>true})
-> 0.0018s
== 20210527194558 CreateCiJobTokenProjectScopeLinks: reverted (0.0119s) =======
== 20210527194558 CreateCiJobTokenProjectScopeLinks: migrating ================
-- create_table(:ci_job_token_project_scope_links, {:if_not_exists=>true})
-> 0.0341s
== 20210527194558 CreateCiJobTokenProjectScopeLinks: migrated (0.0409s) =======
Relevant queries
Ci::JobToken::Scope#all_projects
(plan executed locally):
SELECT "projects".* FROM ((SELECT "projects".* FROM "projects" WHERE "projects"."id" = 9)
UNION ALL
(SELECT "projects".* FROM "projects"
WHERE (EXISTS (
SELECT 1 FROM "ci_job_token_project_scope_links"
WHERE "ci_job_token_project_scope_links"."source_project_id" = 9
AND (projects.id = ci_job_token_project_scope_links.target_project_id)))
)) projects;
Append (cost=0.14..16.51 rows=7 width=1412) (actual time=0.012..0.035 rows=2 loops=1)
-> Index Scan using idx_projects_on_repository_storage_last_repository_updated_at on projects projects_1 (cost=0.14..2.16 rows=1 width=1412) (actual time=0.012..0.013 rows=1 loops=1)
Index Cond: (id = 9)
-> Nested Loop (cost=0.29..14.25 rows=6 width=1412) (actual time=0.021..0.022 rows=1 loops=1)
-> Index Only Scan using i_ci_job_token_project_scope_links_on_source_and_target_project on ci_job_token_project_scope_links (cost=0.15..6.26 rows=6 width=8) (actual time=0.010..0.010 rows=1 loops=1)
Index Cond: (source_project_id = 9)
Heap Fetches: 1
-> Index Scan using idx_projects_on_repository_storage_last_repository_updated_at on projects projects_2 (cost=0.14..1.32 rows=1 width=1412) (actual time=0.005..0.006 rows=1 loops=1)
Index Cond: (id = ci_job_token_project_scope_links.target_project_id)
Planning Time: 0.857 ms
Execution Time: 0.116 ms
(11 rows)
Screenshots (strongly suggested)
Before / When target project is part of the CI job token scope
When job token scope does not include the target project
Does this MR meet the acceptance criteria?
Conformity
-
I have included a changelog entry, or it's not needed. (Does this MR need a changelog?) -
I have added/updated documentation, or it's not needed. (Is documentation required?) -
I have properly separated EE content from FOSS, or this MR is FOSS only. (Where should EE code go?) -
I have added information for database reviewers in the MR description, or it's not needed. (Does this MR have database related changes?) -
I have self-reviewed this MR per code review guidelines. -
This MR does not harm performance, or I have asked a reviewer to help assess the performance impact. (Merge request performance guidelines) -
I have followed the style guides.
Availability and Testing
-
I have added/updated tests following the Testing Guide, or it's not needed. (Consider all test levels. See the Test Planning Process.) - [-] I have tested this MR in all supported browsers, or it's not needed.
- [-] I have informed the Infrastructure department of a default or new setting change per definition of done, or it's not needed.
Security
Does this MR contain changes to processing or storing of credentials or tokens, authorization and authentication methods or other items described in the security review guidelines? If not, then delete this Security section.
-
Label as security and @ mention @gitlab-com/gl-security/appsec
-
The MR includes necessary changes to maintain consistency between UI, API, email, or other methods -
Security reports checked/validated by a reviewer from the AppSec team