Skip to content

Draft: Add groups to CI_JOB_TOKEN allowlist POC

Original issue: #435903 (closed)

What does this MR do and why?

The current configuration for CI_JOB_TOKEN permissions relies on the project allow list, where permitted projects can be specified. The primary objective of the original issue is to expand this functionality by enabling permissions for CI_JOB_TOKEN on specific projects through group associations.

This is the POC of the issue: #435903 (closed).

Allow list implementation:

  1. Similar to Ci::JobToken::ProjectScopeLink, should be introduced Ci::JobToken::GroupScopeLink model/table, source_project_id, target_group_id
  2. Extend Ci::JobToken::Allowlist to support with new methods add_group and remove_group and includes_in_projects_list?(target_project)
  3. Inject to Ci::JobToken::Scope inbound_accessible? method, a condition `group_accessible?(accessed_project) behind a feature flag
  4. Implement group_accessible with the scope validation by matching current_group of current_project, with target_group iterated from Ci::JobToken::GroupScopeLink of the accessible project allow list, more details -

group_accessible? group CI_JOB_TOKEN validation implementation:

The entire process of validating the CI_JOB_TOKEN relies on the idea that we execute pipelines on the current_project and assess permissions to determine if we can reach the accessible_project through the CI_JOB_TOKEN. The process of confirming whether the current_project can access the accesed_project based on current_project group operates like this: we construct a hierarchy of all parent groups on the current_project, such as current_project_group1 -> current_project_parent_groupA_of_group1, and if any of the current_project groups in this list are on the allow list for the accessed_project groups, access will be granted.

To avoid confusion here is the references between used terms as current_project, accessed_project, current_group and codebase:

As we build the group allowlist following convention and naming used on project allowlist:

  1. target_project is a current_project where we run a pipeline and validate if this current_project could access an accessed_project using CI_JOB_TOKEN
  2. target_group is a group of target_proejct also the group field in the table of group allowlist
  3. source_project is an accessed_project that we try to access from the target_proejct and also the project field in the table of group allowlist, determines which project owns the current group allowlist.

API/GraphQL

  1. Introduce a mutation similar to Mutations::Ci::JobTokenScope::AddProject -> Mutations::Ci::JobTokenScope::AddGroup
  2. Mutations::Ci::JobTokenScope::RemoveGroup
  3. Add CIJobToken group allow list field for GraphQL - or extend Ci::JobTokenScopeType with allow list groups list, similar to field :inbound_allowlist

Per feedback: We probably need some method to automatically update reference links when things move around, especially in terms of group accesses. The concern here is the current implementation is static and it for enterprise users, it is highly likely that subgroups or project paths will change with re-organization. Having a way to detect and as an MVC provide error handling will at least notify users that a path is now broken.

For the initial MVP, I propose omitting this step. In the event of changes to subgroups or project paths, it is challenging to promptly notify users of potential broken paths. The current implementation automatically removes a project from the allowlist if it is deleted. I don't foresee scenarios where paths could break, except in cases of deleting a specific group. The lists of projects and subgroups will dynamically expose themselves based on the root group included in the allowlist.

MR acceptance checklist

Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Numbered steps to set up and validate the change are strongly suggested.

Iteration plan

  1. Database design of group list table and model - job_token/group_scope_link - (MR: !142749 (merged))
  2. Add into app/models/ci/job_token/group_scope_link.rb and app/models/ci/job_token/allowlist.rb groups validation together with inbound lists, behind a feature flag group_scope_link_allowlist - !142441 (merged)
  3. GraphQL implementation (MR: !143132 (merged))
  4. REST API implementation

How to setup and validate locally iteration 2

How to set up and validate locally

  1. Enable a FF: ::Feature.enable(:ci_job_token_groups_allowlist)
  2. Create for example private_source_project on the private_source_group
  3. Create a private_target_group
  4. Create a private_target_project in the private_target_group
  5. Generate an artifact on private_source_project by creating .gitlab-ci.yml
create_artifact:
  script:
    - echo "artifacts data" > artifact.txt
  artifacts:
    paths:
      - artifact.txt
  1. Add manually a group into groups allowlist:
  Ci::JobToken::GroupScopeLink.create(source_project: private_source_project, target_group: private_target_group, added_by: user)
  1. Run a pipeline on private_target_project with .gitlab-ci.yml
artifact_download:
  script:
    - echo "test 11"
    - 'curl --location --output artifacts.zip "http://gdk.test:3000/api/v4/projects/47/jobs/10743/artifacts?job_token=$CI_JOB_TOKEN"'
    - ls -la artifacts.zip
    - unzip -p artifacts.zip | cat -

Screenshots

Before or FF is off After and FF is ON
image image image image

How to setup locally Iteration 3

  1. Navigate to the grapqhl explorer

    http://localhost:3000/-/graphql-explorer. # can be different based on gdk config
  2. Copy and paste and run the following queries to the grqphql explorer to test

Fetching the lists of groups

# Fetching the lists of groups
query fetchGroupAllowlists {
  project(fullPath: "flight/flightjsFlight") {
    id
    ciJobTokenScope {
      groupsAllowlist {
        edges {
          node {
            id
          }
        }
      }
    }
  }
}

Response:

{
  "data": {
    "project": {
      "id": "gid://gitlab/Project/38",
      "ciJobTokenScope": {
        "groupsAllowlist": {
          "edges": [
            {
              "node": {
                "id": "gid://gitlab/Group/87"
              }
            },
            {
              "node": {
                "id": "gid://gitlab/Group/25"
              }
            }
          ]
        }
      }
    }
  }
}

Add a group to allow list

mutation addGroup {
  ciJobTokenScopeAddGroup(
    input: {
      projectPath: "flight/flightjsFlight",
      targetGroupPath: "new_group"
    }
  ) {
    errors,
    clientMutationId,
    ciJobTokenScope {
      groupsAllowlist {
        edges {
          node {
            id
          }
        }
      }
    }
  }
}

Response:

{
  "data": {
    "ciJobTokenScopeAddGroup": {
      "errors": [],
      "clientMutationId": null,
      "ciJobTokenScope": {
        "groupsAllowlist": {
          "edges": [
            {
              "node": {
                "id": "gid://gitlab/Group/87"
              }
            },
            {
              "node": {
                "id": "gid://gitlab/Group/27"
              }
            }
          ]
        }
      }
    }
  }
}

Remove a group from allow list

mutation removeGroup {
  ciJobTokenScopeRemoveGroup(
    input: {
      projectPath: "flight/flightjsFlight",
      targetGroupPath: "newgroup"
    }
  ) {
    errors,
    clientMutationId,
    ciJobTokenScope {
      groupsAllowlist {
        edges {
          node {
            id
          }
        }
      }
    }
  }
}

Response:

{
  "data": {
    "ciJobTokenScopeRemoveGroup": {
      "errors": [],
      "clientMutationId": null,
      "ciJobTokenScope": {
        "groupsAllowlist": {
          "edges": [
            {
              "node": {
                "id": "gid://gitlab/Group/27"
              }
            }
          ]
        }
      }
    }
  }
}
Edited by Dmytro Biryukov

Merge request reports