Skip to content

Draft: Resolve "Allow protecting container repositories against writes [POC]"

What does this MR do and why?

This MR will not be merged. The intention is to discuss and document design decision and implemenation details.

This MR is just a proof of concept for the &9825 and #18984 (closed) .

A proposal for implementation plan can be found in #18984 (comment 1447200733)

🛠 with at Siemens

Proposal for data model

  • In the current implementation, the container registry uses the GitLab as the authentication service because GitLab knows about the users and their connections to projects, etc. => this means, that when sending docker commands to the container registry, GitLab receives auth-related requests, e.g.:
    • For the command docker login registry.test:5000 -u gitlab-token", the container registry sends the following auth request: GET "/jwt/auth?account=gitlab-token&client_id=docker&offline_token=[FILTERED]&service=container_registry" with params {"account"=>"gitlab-token", "client_id"=>"docker", "offline_token"=>"[FILTERED]", "service"=>"container_registry"}
    • For the command docker push flightjs/flight:v1.0, the container registry sends the following auth request: GET "/jwt/auth?account=gitlab-token&scope=repository%3Aflightjs%2Fflight%3Apush%2Cpull&service=container_registry" with params {"account"=>"gitlab-token", "scope"=>"repository:flightjs/flight:push,pull", "service"=>"container_registry"}
  • I propose to tap into this logic / sequence and check if the respective container repository is protected or not, see the following sequence
sequenceDiagram
    actor User
    User->>+registry.test : docker push registry.test:5000/flightjs/flight:v1.17 
    registry.test->>+GitLab: GET "/jwt/auth?account=gitlab-token&scope=repository%3Aflightjs%2Fflight%3Apush%2Cpull&service=container_registry"
    GitLab->>GitLab: JwtController
    GitLab->>GitLab: Auth::ContainerRegistryAuthenticationService
    GitLab->>GitLab: Check ContainerRegistry::ContainerRepositoryProtectionRule
    GitLab-->>-registry.test: HTTP response code 200 (SUCCESS)
    registry.test->>-registry.test: Store container image flightjs/flight:v1.0
    registry.test-->>User: SUCCESS MESSAGE
  • The ContainerRegistry::ContainerRepositoryProtectionRule is a new model that contains the information about which container repository is protected or not.
classDiagram
  class `ContainerRegistry::ContainerRepositoryProtectionRule`{
    id bigint,
    container_path string
    matching_package_version string
    push_protected_up_to_access_level Gitlab::Access
    delete_protected_up_to_access_level Gitlab::Access
    project_id bigint 
    namespace_id bigint
  }
  `ContainerRegistry::ContainerRepositoryProtectionRule` --> `Project`

  `ContainerRepository` --> `Project`
  `Project` --> `Group (Namespace)`

Screenshots or screen recordings

The following console output shows the expected behavior. In the first part, the protection rule is set to no access and all container images with the name flightjs/flight are protected. In the second part, we protect only against developer roles and pushing the image is accepted because we push with the root user.

➜  gitlab git:(18984-feature-protected-containers-proof-of-concept) ✗ rails r 'ContainerRegistry::ContainerRepositoryProtectionRule.first.update(push_protected_up_to_access_level: Gitlab::Access::NO_ACCESS)'
➜  gitlab git:(18984-feature-protected-containers-proof-of-concept) ✗ docker push registry.test:5000/flightjs/flight:v1.17                                                             
The push refers to repository [registry.test:5000/flightjs/flight]
a7866053acac: Preparing 
denied: container protected; you are not allowed to push new package

➜  gitlab git:(18984-feature-protected-containers-proof-of-concept) ✗ rails r 'ContainerRegistry::ContainerRepositoryProtectionRule.first.update(push_protected_up_to_access_level: Gitlab::Access::DEVELOPER)'
➜  gitlab git:(18984-feature-protected-containers-proof-of-concept) ✗ docker push registry.test:5000/flightjs/flight:v1.17                                                              
The push refers to repository [registry.test:5000/flightjs/flight]
a7866053acac: Layer already exists 
v1.17: digest: sha256:efebf0f7aee69450f99deafe11121afa720abed733943e50581a9dc7540689c8 size: 525

How to set up and validate locally

  1. Enable the container registry, see https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/howto/registry.md (<= setup as HTTP and not HTTPS)
  2. rails db:migrate to create the new table for ContainerRepositoryProtectionRule
  3. Create a ContainerRepositoryProtectionRule to protect against pushing new container images with the name "flightjs/flight"
rails r 'ContainerRegistry::ContainerRepositoryProtectionRule.create(container_path: "flightjs/flight", push_protected_up_to_access_level: Gitlab::Access::NO_ACCESS, delete_protected_up_to_access_level: Gitlab::Access::NO_ACCESS, project: Project.find(7), namespace: Project.find(7).namespace)'
  1. In another terminal session, prepare your container image
docker login registry.test:5000 -u gitlab-token -p "ypCa3Dzb23o5nvsixwPA"
docker pull hello-world:latest
docker tag hello-world:latest registry.test:5000/flightjs/flight:v1.0
  1. Push the container image docker push registry.test:5000/flightjs/flight:v1.0 => will be blocked by ContainerRepositoryProtectionRule

Personal Notes

MR acceptance checklist

This MR will not be merged. The intention is to discuss and document design decision and implemenation details.

Related to Allow protecting container repositories against... (#18984 - closed) and Container Registry: Granular protection for rep... (&9825)

Edited by Gerardo Navarro

Merge request reports