Skip to content

Restrict JWT requests when importing repositories

Steve Abrams requested to merge 349743-phase2-jwt into master

🎻 Context

We are preparing for Phase 2 of the Container Registry migration which involves importing all existing container repositories to the new platform (Phase 1 involved routing all new container repositories to the new platform). See &7316 (closed) for full details of how the import will work.

The container registry uses JWTs generated by the jwt_controller via the ContainerRegistryAuthenticationService to grant users access to the registry. When a container repository is being imported, we need to ensure no tokens are granted certain types of access that could cause problems during the import. Additionally, the registry will need to make requests to import container repositories, so it would be best to use a new token scope to make those requests so they have no risk of being confused with other user access requests.

🔎 What does this MR do and why?

Rejects JWT token requests in the Container Registry authentication service if the requested scope is one of:

  • delete
  • push
  • *

and the requested container repository is in an importing migration state.

We also introduce a new scope to the service: import. This scope will be used by the container registry when making requests to rails to import the next repository. The new scope is only accessible via a new helper method: import_access_token. We will use this method directly in the workers that perform the imports so users will never be able to request this scope.

📸 Screenshots or screen recordings

The How to set up and validate locally below shows the output from the commands I ran locally.

How to set up and validate locally

Testing token rejection on import

  1. Create a container repository using the rails console:

    [1] pry(main)> c = ContainerRepository.create(project: Project.first, name: '')
  2. Get the path of the new container repository:

    [2] pry(main)> c.path
    => "gitlab-org/gitlab-test"
  3. In your terminal request a token with a delete, push, or * scope for the repository. You will need a personal access token. Also, ensure the user you are using has at least developer access to the project you used when creating the container repository:

    $ curl --user "root:<personal_access_token>" "http://gdk.test:3001/jwt/auth? 
    client_id=docker&service=container_registry&scope=repository:<repository_path>:delete" | jq -r '.token' | jwt decode -
    
    ...
    
    Token claims
    ------------
    {
      "access": [
        {
          "actions": [
            "delete"
          ],
          "name": "gitlab-org/gitlab-test",
          "type": "repository"
        }
      ],
      ...
    }

    In the access section of the output, you should see the action you requested for the repository_path you requested.

    Note: I've used jq and jwt in this command, if you do not have those CLI tools installed, you can take the token value from the response payload and use a JWT decoder to see the contents.

  4. In the rails console, change the container repository migration_state to 'importing':

    [3] pry(main)> ContainerRepository.last.update_column(:migration_state, 'importing')
  5. In the terminal, make the same request for the JWT (leave off the jwt command since no JWT is returned):

    → curl --user "root:<personal_access_token>" "http://gdk.test:3001/jwt/auth?client_id=docker&service=container_registry&scope=repository:gitlab-org/gitlab-test:delete" | jq
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100   233  100   233    0     0    714      0 --:--:-- --:--:-- --:--:--   730
    {
      "errors": [
        {
          "code": "DENIED",
          "message": "Your repository is currently being migrated to a new platform and writes are temporarily disabled. Go to https://gitlab.com/groups/gitlab-org/-/epics/5523 to learn more."
        }
      ],
      "http_status": 403
    }

    The response should contain an error.

    Testing with both a valid and invalid scope should also result in an error: curl --user "root:<personal_access_token>" "http://gdk.test:3001/jwt/auth?client_id=docker&service=container_registry&scope=repository:gitlab-org/gitlab-test:pull,push"

Testing a JWT import token scope

  1. Assuming you have created a container repository from the test above, in the rails console, create an import token for it (this example shows the project path of gitlab-org/gitlab-test, use the project path associated with your local container repository):

    [14] pry(main)> token = Auth::ContainerRegistryAuthenticationService.import_access_token('gitlab-org/gitlab-test')
    [15] pry(main)> JWT.decode(token, nil, false)
    => [{"access"=>[{"type"=>"repository", "name"=>"gitlab-org/gitlab-test", "actions"=>["import"]}], ....]

    You should see the import scope present in the actions section of the payload for the project you used.

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Related to #349743 (closed)

Edited by Steve Abrams

Merge request reports