Skip to content

Add scope checks when using Dependency Proxy with group access tokens

Context 🌱

We added Dependency Proxy support for group access tokens in !128583 (merged) but didn't add checks on the token scopes.

This MR adds those checks, and also adds missing controller specs for deploy tokens.

Dependency Proxy Authentication Overview

This blog post gives a brief overview of Dependency Proxy authentication.

To pull an image from Dependency Proxy, the user does a docker login <gitlab_url> -p <token>, and then docker pull <gitlab_url>/<group_path>/dependency_proxy/containers/<image_name>:<image_tag>

What happens behind the scenes:

docker login:

  • /v2/ -> Groups::DependencyProxyAuthController#authenticate
  • /jwt/auth -> JwtController#auth

docker pull:

  • /v2/ -> Groups::DependencyProxyAuthController#authenticate
  • /jwt/auth -> JwtController#auth
  • /v2/<group_path>/dependency_proxy/containers/<image_name>/manifests/<image_tag> -> Groups::DependencyProxyForContainersController#manifest

Problem

docker login and docker pull requests from a group access token with insufficient scopes for Dependency Proxy are not rejected.

When dealing with a group access token in a policy (in GroupPolicy, to be specific), we are passed only the token user and the dependency proxy group. We do not have a handle to the actual token. We considered doing bot_user.personal_access_tokens.first but decided it will not work, because it is possible to add tokens even to a bot user. As such, we cannot determine if a group access token has the correct scopes in a policy code. We'll have to do the checking in the controller.

Solution

  • Inside the authenticate_with_http_basic block in JwtController, memoize raw_token
  • Include raw_token in the parameters passed to Auth::DependencyProxyAuthenticationService
  • Do PersonalAccessToken.find_by_token(raw_token) in Auth::DependencyProxyAuthenticationService
  • In the policy, because we cannot get a handle to the token, we only check if the project bot user is a member of the subject (dependency proxy group)

In JwtController#auth we verify:

  • if the token is an active token
  • if the token has the required scopes for dependency proxy
  • we do not verify if the token user has the correct access level to the dependency proxy group

In GroupPolicy we verify:

  • if the token is an active token
  • if the token user is a member of the dependency proxy group
  • Because we do not have a handle to the token,
    • we cannot verify scopes
    • we cannot verify if the token is active or not

Taken together, our checks in JwtController#auth and in GroupPolicy will correctly reject

  • tokens that are not active
  • tokens without the required scopes for dependency proxy
  • tokens that are not a group access token for the dependency proxy group

MR Summary

  • Modify JwtController to pass the raw token to Auth::DependencyProxyAuthenticationService
  • Modify Auth::DependencyProxyAuthenticationService - reject the login if the passed project bot or deploy token does not have the required scopes. Also refactored to make the 3 checks clearer (human user, group access token user, deploy token)
  • Refactor the dependency proxy policy to make it clearer what we're checking for:
    • a valid human user, or
    • a valid group access token, or
    • a valid deploy token
  • Modify Auth::DependencyProxyAuthenticationService specs
    • Instead of simply testing for a non-nil token, test that we're putting in the correct content in the encoded token (user_id for personal access tokens, deploy_token for deploy tokens)
  • Update the group policy specs to test that we pass/fail correctly for human users, group access token, and deploy tokens
  • Update the Groups::DependencyProxyForContainers specs
    • Add missing specs that validates that deploy tokens are authorized uploads
    • Add specs for invalid group access tokens
    • Add specs for valid group access tokens

How to set up and validate locally

Enable Dependency Proxy for a group

A group access token with insufficient scopes should fail docker login

  1. Create a group access token for the group. Give it only the read_registry scope.
  2. Clear docker credentials: docker logout http://gdk.test:3000
  3. Login: docker login http://gdk.test:3000 -p <group_access_token>. The login should fail.

A group access token with sufficient scopes should be able to docker pull

  1. Create another group access token, this time give it both read_registry and write_registry scopes.
  2. Login with the newer token. It should be successful.
  3. Pull an image: docker pull gdk.test:3000/<group-namespace>/dependency_proxy/containers/alpine:latest. This should also be successful.
  4. Open the group dependency proxy page (Group home -> Operate -> Dependency Proxy, or http://gdk.test:3000/groups/<group-namespace>/-/dependency_proxy), you should see the pulled image in the list of images

A revoked group access token, even with sufficient scopes, should fail docker login and docker pull

  1. Revoke the token used for the successful pull in the previous step. From the Rails console, run PersonalAccessToken.last.revoke!
  2. Try the docker pull again: docker pull gdk.test:3000/<group-namespace>/dependency_proxy/containers/alpine:latest. This time the operation should fail with a forbidden response. This confirms that we do our auth checks for every operation, even if the user is already logged in.
  3. Try docker login again with the token. This time the login should fail.

A group access token with api scope should be able to docker pull

The api scope grants read_registry and write_registry access (documentation).

  1. Create a new group access token, this time give it only the api scope.
  2. docker login and docker pull should be both successful just like A group access token with sufficient scopes

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 #332411

Edited by Radamanthus Batnag

Merge request reports