Skip to content

Allow anyone to pull public NuGet packages on group level

🔭 Context

ّIn Feature specific permissions for the Package R... (#329253 - closed), it was suggested to have a way to have a public package registry in a non-public (private or internal) projects.

We quickly discovered challenges for endpoints on the group-level. Thus, we decided to scope that issue to project-level endpoints only (see #329253 (comment 1120283554)).

🔥 Problem

The problem is twofold here:

  • User might want to be able to pull packages from a public package registry (in a non-public project) at the group-level endpoint.
  • Some formats don't have project-level endpoints, for example Composer. As such, the Allow anyone to pull toggle will not work for Composer packages.

🚒 Solution

We have two aspects to consider in the path to the solution:

  1. On the group level endpoints, the requests to pull packages are authorized by checking the permission :read_group. The :read_group permission is granted to a request based on some conditions.

    Now we have an additional condition that we need to introduce: if a group has projects with public registries, grant the :read_group permission to the request.

    However, we cannot simply do that, because adding a new condition will make it subject to be evaluated by any request. While in fact, we only need it in a narrow, specific context: when pulling a package on the group-level. And given that the :read_group permission is used across all the codebase by many requests, we cannot add such a package-scoped condition to it.

    The solution of this issue would be by introducing a new permission. The new permission will be used exclusively in the context of pulling packages on the group-level endpoints, and will live in the Packages::Policies::GroupPolicy class. In this policy, we have all rules we want to apply on packages on the group level.

  2. At the group level, we have finders to collect all the projects that a user can access to find a package. This part uses the user access level.

    In short, we have a SQL query that says: within this group, collect all the public projects + all the projects where the user has reporter access.

    We will need to update that to: within this group, collect all the public projects + all the projects where the user has reporter access + all the projects that have a public package registry.

What does this MR do and why?

  • Introducing a new permission read_package_in_group to authorize a request to pull a package on the group level endpoint. The permission lives in the Packages::Policies::GroupPolicy class and will be evaluated only after checking the read_group permission. If the request was granted the read_group permission, then no need to check the read_package_in_group one. Otherwise, it's evaluated.

  • Modify the SQL query used in the package finder to be: within this group, collect all the public projects + all the projects where the user has reporter access + all the projects that have a public package registry.

  • Modify authorization methods to be ready to evaluate the read_package_in_group new permission on a group.

  • Add the needed tests.

  • Scope the changes to only NuGet Repository's metadata group level endpoints behind a feature flag. It would be easier & debug if the changes are scoped to one format. After the solution proves to be working fine, we can extend it to the other package formats.

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.

Screenshots or screen recordings

N/A

How to set up and validate locally

  1. Make sure you have a private project in a private group to use to publish the package in.
  2. Open rails console:
    # Enable the ~"feature flag"
    Feature.enable(:allow_anyone_to_pull_public_nuget_packages_on_group_level)
    
    # Enable `package_registry_allow_anyone_to_pull_option` application setting
    ApplicationSetting.last.update(package_registry_allow_anyone_to_pull_option: true)
    
    # Enable Allow anyone to pull from Package Registry in the private project from step 1
    Project.find(<id>).project_feature.update(package_registry_access_level: ::ProjectFeature::PUBLIC)
    
    # Create an external user that we are sure they dont have access to the group or project
    user = FactoryBot.create(:user, :external)
    # Keep the username of the user, we will use it later
    user.username
    
    # Create PAT for the external user, we will use it later
    pat =  FactoryBot.create(:personal_access_token, user: ext).token
    
    # stub file upload
    def fixture_file_upload(*args, **kwargs)
      Rack::Test::UploadedFile.new(*args, **kwargs)
    end
    
    # Create a nuget package in the private project from step 1
    package = FactoryBot.create(:nuget_package, project_id: <private_project_id>)
    # Keep the package name, we will use it later
    package.name
  3. We can now try installing the package using NuGet CLI:
    nuget install NugetPackage1 -OutputDirectory <output_directory> -Source "http://gdk.test:3000/api/v4/groups/<private_group_id>/-/packages/nuget/index.json"
    nuget install will show a prompt asking for username and password, so paste what we got from rails console (the external username & pat)
  4. The package should be installed successfully.
  5. Clear NuGet cache, and delete the installed package from the local output directory on your machine, and disable Allow anyone to pull from Package Registry in the private project:
    Project.find(<id>).project_feature.update(package_registry_access_level: ::ProjectFeature::ENABLED)
  6. Repeat step 3: 404 Not found

Related to #383537 (closed) & #468058 (closed)

Edited by Moaz Khalifa

Merge request reports

Loading