diff options
29 files changed, 236 insertions, 109 deletions
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index 989013df8d4..6b842fc9fe1 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -114,7 +114,7 @@ module Groups end def deploy_token_params - params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :username) + params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :username) end end end diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 5feb3e019c2..a0f98d8f1d2 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -93,7 +93,7 @@ module Projects end def deploy_token_params - params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :username) + params.require(:deploy_token).permit(:name, :expires_at, :read_repository, :read_registry, :write_registry, :username) end def run_autodevops_pipeline(service) diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb index a9844f627b7..69245710f01 100644 --- a/app/models/deploy_token.rb +++ b/app/models/deploy_token.rb @@ -7,7 +7,7 @@ class DeployToken < ApplicationRecord include Gitlab::Utils::StrongMemoize add_authentication_token_field :token, encrypted: :optional - AVAILABLE_SCOPES = %i(read_repository read_registry).freeze + AVAILABLE_SCOPES = %i(read_repository read_registry write_registry).freeze GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token' default_value_for(:expires_at) { Forever.date } @@ -105,7 +105,7 @@ class DeployToken < ApplicationRecord end def ensure_at_least_one_scope - errors.add(:base, _("Scopes can't be blank")) unless read_repository || read_registry + errors.add(:base, _("Scopes can't be blank")) unless read_repository || read_registry || write_registry end def default_username diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 629c1cbdc5c..4a699fe3213 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -135,7 +135,7 @@ module Auth when 'pull' build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project) when 'push' - build_can_push?(requested_project) || user_can_push?(requested_project) + build_can_push?(requested_project) || user_can_push?(requested_project) || deploy_token_can_push?(requested_project) when 'delete' build_can_delete?(requested_project) || user_can_admin?(requested_project) when '*' @@ -185,6 +185,13 @@ module Auth current_user.read_registry? end + def deploy_token_can_push?(requested_project) + has_authentication_ability?(:create_container_image) && + current_user.is_a?(DeployToken) && + current_user.has_access_to?(requested_project) && + current_user.write_registry? + end + ## # We still support legacy pipeline triggers which do not have associated # actor. New permissions model and new triggers are always associated with diff --git a/app/services/projects/update_repository_storage_service.rb b/app/services/projects/update_repository_storage_service.rb index 0602089a3ab..2e5de9411d1 100644 --- a/app/services/projects/update_repository_storage_service.rb +++ b/app/services/projects/update_repository_storage_service.rb @@ -5,12 +5,15 @@ module Projects include Gitlab::ShellAdapter Error = Class.new(StandardError) + SameFilesystemError = Class.new(Error) def initialize(project) @project = project end def execute(new_repository_storage_key) + raise SameFilesystemError if same_filesystem?(project.repository.storage, new_repository_storage_key) + mirror_repositories(new_repository_storage_key) mark_old_paths_for_archive @@ -33,6 +36,10 @@ module Projects private + def same_filesystem?(old_storage, new_storage) + Gitlab::GitalyClient.filesystem_id(old_storage) == Gitlab::GitalyClient.filesystem_id(new_storage) + end + def mirror_repositories(new_repository_storage_key) mirror_repository(new_repository_storage_key) diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index ab2f64cdc21..c0f60b5f3b1 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -4,7 +4,7 @@ - expanded = expanded_by_default? - general_expanded = @project.errors.empty? ? expanded : true -- deploy_token_description = s_('DeployTokens|Deploy tokens allow read-only access to your repository and registry images.') +- deploy_token_description = s_('DeployTokens|Deploy tokens allow access to your repository and registry images.') %section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) } .settings-header diff --git a/app/views/shared/deploy_tokens/_form.html.haml b/app/views/shared/deploy_tokens/_form.html.haml index c4e82d8e157..5751ed9cb7a 100644 --- a/app/views/shared/deploy_tokens/_form.html.haml +++ b/app/views/shared/deploy_tokens/_form.html.haml @@ -30,5 +30,10 @@ = label_tag ("deploy_token_read_registry"), 'read_registry', class: 'label-bold form-check-label' .text-secondary= s_('DeployTokens|Allows read-only access to the registry images') + %fieldset.form-group.form-check + = f.check_box :write_registry, class: 'form-check-input' + = label_tag ("deploy_token_write_registry"), 'write_registry', class: 'label-bold form-check-label' + .text-secondary= s_('DeployTokens|Allows write access to the registry images') + .prepend-top-default = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success qa-create-deploy-token' diff --git a/app/workers/project_update_repository_storage_worker.rb b/app/workers/project_update_repository_storage_worker.rb index bb40107494b..ecee33e6421 100644 --- a/app/workers/project_update_repository_storage_worker.rb +++ b/app/workers/project_update_repository_storage_worker.rb @@ -3,21 +3,11 @@ class ProjectUpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker include ApplicationWorker - SameFilesystemError = Class.new(StandardError) - feature_category :gitaly def perform(project_id, new_repository_storage_key) project = Project.find(project_id) - raise SameFilesystemError if same_filesystem?(project.repository.storage, new_repository_storage_key) - ::Projects::UpdateRepositoryStorageService.new(project).execute(new_repository_storage_key) end - - private - - def same_filesystem?(old_storage, new_storage) - Gitlab::GitalyClient.filesystem_id(old_storage) == Gitlab::GitalyClient.filesystem_id(new_storage) - end end diff --git a/changelogs/unreleased/22743-deploy-token-write-registry.yml b/changelogs/unreleased/22743-deploy-token-write-registry.yml new file mode 100644 index 00000000000..842ef95446d --- /dev/null +++ b/changelogs/unreleased/22743-deploy-token-write-registry.yml @@ -0,0 +1,5 @@ +--- +title: Add write_registry scope to deploy tokens for container registry push access +merge_request: 28958 +author: +type: added diff --git a/db/migrate/20200406192059_add_write_registry_to_deploy_tokens.rb b/db/migrate/20200406192059_add_write_registry_to_deploy_tokens.rb new file mode 100644 index 00000000000..22fdb030edc --- /dev/null +++ b/db/migrate/20200406192059_add_write_registry_to_deploy_tokens.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddWriteRegistryToDeployTokens < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:deploy_tokens, :write_registry, :boolean, default: false, allow_null: false) + end + + def down + remove_column(:deploy_tokens, :write_registry) + end +end diff --git a/db/structure.sql b/db/structure.sql index 90585a157cf..622234396a2 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1997,7 +1997,8 @@ CREATE TABLE public.deploy_tokens ( token character varying, username character varying, token_encrypted character varying(255), - deploy_token_type smallint DEFAULT 2 NOT NULL + deploy_token_type smallint DEFAULT 2 NOT NULL, + write_registry boolean DEFAULT false NOT NULL ); CREATE SEQUENCE public.deploy_tokens_id_seq @@ -13087,6 +13088,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200403185127 20200403185422 20200406135648 +20200406192059 20200407094005 20200407094923 20200408110856 diff --git a/doc/administration/availability/index.md b/doc/administration/availability/index.md index 90113985ad5..a0d4ea7919f 100644 --- a/doc/administration/availability/index.md +++ b/doc/administration/availability/index.md @@ -26,6 +26,14 @@ watch [this 1 hour Q&A](https://www.youtube.com/watch?v=uCU8jdYzpac) with [John Northrup](https://gitlab.com/northrup), and live questions coming in from some of our customers. +GitLab offers a number of options to manage availability and resiliency. Below are the options to consider with trade-offs. + +| Event | GitLab Feature | Recovery Point Objective (RPO) | Recovery Time Objective (RTO) | Cost | +| ----- | -------------- | --- | --- | ---- | +| Availability Zone failure | "GitLab HA" | No loss | No loss | 2x Git storage, multiple nodes balanced across AZ's | +| Region failure | "GitLab Disaster Recovery" | 5-10 minutes | 30 minutes | 2x primary cost | +| All failures | Backup/Restore | Last backup | Hours to Days | Cost of storing the backups | + ## High availability ### Omnibus installation with automatic database failover diff --git a/doc/api/deploy_tokens.md b/doc/api/deploy_tokens.md index 4663159f1eb..461957847df 100644 --- a/doc/api/deploy_tokens.md +++ b/doc/api/deploy_tokens.md @@ -92,7 +92,7 @@ POST /projects/:id/deploy_tokens | `name` | string | yes | New deploy token's name | | `expires_at` | datetime | no | Expiration date for the deploy token. Does not expire if no value is provided. | | `username` | string | no | Username for deploy token. Default is `gitlab+deploy-token-{n}` | -| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository` or `read_registry`. | +| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, or `write_registry`. | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "My deploy token", "expires_at": "2021-01-01", "username": "custom-user", "scopes": ["read_repository"]}' "https://gitlab.example.com/api/v4/projects/5/deploy_tokens/" @@ -193,7 +193,7 @@ POST /groups/:id/deploy_tokens | `name` | string | yes | New deploy token's name | | `expires_at` | datetime | no | Expiration date for the deploy token. Does not expire if no value is provided. | | `username` | string | no | Username for deploy token. Default is `gitlab+deploy-token-{n}` | -| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository` or `read_registry`. | +| `scopes` | array of strings | yes | Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, or `write_registry`. | Example request: diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md index e2868d648de..f3488b45bbb 100644 --- a/doc/subscriptions/index.md +++ b/doc/subscriptions/index.md @@ -75,6 +75,10 @@ count as active users in the subscription period in which they were originally a - Members with Guest permissions on an Ultimate subscription. - GitLab-created service accounts: `Ghost User` and `Support Bot`. +##### User Statistics + +A breakdown of the users within your instance including active, billable and blocked can be found by navigating to **Admin Area > Overview > Dashboard** and selecting `Users Statistics` button within the `Users` widget.. + NOTE: **Note:** If you have LDAP integration enabled, anyone in the configured domain can sign up for a GitLab account. This can result in an unexpected bill at time of renewal. Consider [disabling new signups](../user/admin_area/settings/sign_up_restrictions.md) and managing new users manually instead. diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index c65d6adcff6..57d2a383768 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -463,10 +463,41 @@ The DAST job does not require the project's repository to be present when runnin ## Running DAST in an offline environment -DAST can be executed on an offline GitLab Ultimate installation by using the following process: +For self-managed GitLab instances in an environment with limited, restricted, or intermittent access +to external resources through the internet, some adjustments are required for the DAST job to +successfully run. For more information, see [Offline environments](../offline_deployments/index.md). + +### Requirements for offline DAST support + +To use DAST in an offline environment, you need: + +- GitLab Runner with the [`docker` or `kubernetes` executor](#requirements). +- Docker Container Registry with a locally available copy of the DAST [container image](https://gitlab.com/gitlab-org/security-products/dast), found in the [DAST container registry](https://gitlab.com/gitlab-org/security-products/dast/container_registry). + +NOTE: **Note:** +GitLab Runner has a [default `pull policy` of `always`](https://docs.gitlab.com/runner/executors/docker.html#using-the-always-pull-policy), +meaning the runner may try to pull remote images even if a local copy is available. Set GitLab +Runner's [`pull_policy` to `if-not-present`](https://docs.gitlab.com/runner/executors/docker.html#using-the-if-not-present-pull-policy) +in an offline environment if you prefer using only locally available Docker images. + +### Make GitLab DAST analyzer images available inside your Docker registry + +For DAST, import the following default DAST analyzer image from `registry.gitlab.com` to your local "offline" +registry: + +- `registry.gitlab.com/gitlab-org/security-products/dast:latest` + +The process for importing Docker images into a local offline Docker registry depends on +**your network security policy**. Please consult your IT staff to find an accepted and approved +process by which external resources can be imported or temporarily accessed. Note that these scanners are [updated periodically](../index.md#maintenance-and-update-of-the-vulnerabilities-database) +with new definitions, so consider if you are able to make periodic updates yourself. + +For details on saving and transporting Docker images as a file, see Docker's documentation on +[`docker save`](https://docs.docker.com/engine/reference/commandline/save/), [`docker load`](https://docs.docker.com/engine/reference/commandline/load/), +[`docker export`](https://docs.docker.com/engine/reference/commandline/export/), and [`docker import`](https://docs.docker.com/engine/reference/commandline/import/). + +### Set DAST CI job variables to use local DAST analyzers -1. Host the DAST image `registry.gitlab.com/gitlab-org/security-products/dast:latest` in your local - Docker container registry. 1. Add the following configuration to your `.gitlab-ci.yml` file. You must replace `image` to refer to the DAST Docker image hosted on your local Docker container registry: diff --git a/doc/user/project/deploy_tokens/img/deploy_tokens.png b/doc/user/project/deploy_tokens/img/deploy_tokens.png Binary files differindex 493de8e0fce..afe1dfb922f 100644 --- a/doc/user/project/deploy_tokens/img/deploy_tokens.png +++ b/doc/user/project/deploy_tokens/img/deploy_tokens.png diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md index 0bd511cf837..ebb12a6ed5d 100644 --- a/doc/user/project/deploy_tokens/index.md +++ b/doc/user/project/deploy_tokens/index.md @@ -2,8 +2,9 @@ > - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17894) in GitLab 10.7. > - [Moved](https://gitlab.com/gitlab-org/gitlab/issues/199370) from **Settings > Repository** in GitLab 12.9. +> - [Added `write_registry` scope](https://gitlab.com/gitlab-org/gitlab/-/issues/22743) in GitLab 12.10. -Deploy tokens allow you to download (`git clone`) or read the container registry images of a project without having a user and a password. +Deploy tokens allow you to download (`git clone`) or push and pull the container registry images of a project without having a user and a password. Deploy tokens can be managed by [maintainers only](../../permissions.md). @@ -44,6 +45,7 @@ the following table. | ----- | ----------- | | `read_repository` | Allows read-access to the repository through `git clone` | | `read_registry` | Allows read-access to [container registry](../../packages/container_registry/index.md) images if a project is private and authorization is required. | +| `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). | ## Deploy token custom username @@ -83,6 +85,21 @@ docker login -u <username> -p <deploy_token> registry.example.com Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply pull images from your Container Registry. +### Push Container Registry images + +To push the container registry images, you'll need to: + +1. Create a Deploy Token with `write_registry` as a scope. +1. Take note of your `username` and `token`. +1. Log in to GitLab’s Container Registry using the deploy token: + + ```shell + docker login -u <username> -p <deploy_token> registry.example.com + ``` + +Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply +push images to your Container Registry. + ### Group Deploy Token > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/21765) in GitLab 12.9. @@ -107,7 +124,7 @@ There's a special case when it comes to Deploy Tokens. If a user creates one named `gitlab-deploy-token`, the username and token of the Deploy Token will be automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and `CI_DEPLOY_PASSWORD`, respectively. With the GitLab Deploy Token, the -`read_registry` scope is implied. +`read_registry` and `write_registry` scopes are implied. After you create the token, you can login to the Container Registry using those variables: diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb index d36b75f5bfd..5de36c14d7b 100644 --- a/lib/api/deploy_tokens.rb +++ b/lib/api/deploy_tokens.rb @@ -10,6 +10,7 @@ module API result_hash = {} result_hash[:read_registry] = scopes.include?('read_registry') + result_hash[:write_registry] = scopes.include?('write_registry') result_hash[:read_repository] = scopes.include?('read_repository') result_hash end @@ -54,7 +55,7 @@ module API params do requires :name, type: String, desc: "New deploy token's name" requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository" or "read_registry".' + desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", or "write_registry".' optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`' end @@ -117,7 +118,7 @@ module API params do requires :name, type: String, desc: 'The name of the deploy token' requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository" or "read_registry".' + desc: 'Indicates the deploy token scopes. Must be at least one of "read_repository", "read_registry", or "write_registry".' optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided.' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`' end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index c489c835d9d..8e14d21f591 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -12,7 +12,7 @@ module Gitlab REPOSITORY_SCOPES = [:read_repository, :write_repository].freeze # Scopes used for GitLab Docker Registry access - REGISTRY_SCOPES = [:read_registry].freeze + REGISTRY_SCOPES = [:read_registry, :write_registry].freeze # Scopes used for GitLab as admin ADMIN_SCOPES = [:sudo].freeze @@ -200,6 +200,7 @@ module Gitlab api: full_authentication_abilities, read_api: read_only_authentication_abilities, read_registry: [:read_container_image], + write_registry: [:create_container_image], read_repository: [:download_code], write_repository: [:download_code, :push_code] } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fb2f7c60ae2..14fc1487b5e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6835,6 +6835,9 @@ msgstr "" msgid "DeployTokens|Allows read-only access to the repository" msgstr "" +msgid "DeployTokens|Allows write access to the registry images" +msgstr "" + msgid "DeployTokens|Copy deploy token" msgstr "" @@ -6853,7 +6856,7 @@ msgstr "" msgid "DeployTokens|Deploy Tokens" msgstr "" -msgid "DeployTokens|Deploy tokens allow read-only access to your repository and registry images." +msgid "DeployTokens|Deploy tokens allow access to your repository and registry images." msgstr "" msgid "DeployTokens|Expires" diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb index e86d4ab8812..657915f9976 100644 --- a/spec/factories/deploy_tokens.rb +++ b/spec/factories/deploy_tokens.rb @@ -7,6 +7,7 @@ FactoryBot.define do sequence(:name) { |n| "PDT #{n}" } read_repository { true } read_registry { true } + write_registry { false } revoked { false } expires_at { 5.days.from_now } deploy_token_type { DeployToken.deploy_token_types[:project_type] } diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index ce60a19a7b3..a0a8767637e 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it 'optional_scopes contains all non-default scopes' do stub_container_registry_config(enabled: true) - expect(subject.optional_scopes).to eq %i[read_user read_api read_repository write_repository read_registry sudo openid profile email] + expect(subject.optional_scopes).to eq %i[read_user read_api read_repository write_repository read_registry write_registry sudo openid profile email] end end @@ -38,21 +38,21 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do it 'contains all non-default scopes' do stub_container_registry_config(enabled: true) - expect(subject.all_available_scopes).to eq %i[api read_user read_api read_repository write_repository read_registry sudo] + expect(subject.all_available_scopes).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry sudo] end it 'contains for non-admin user all non-default scopes without ADMIN access' do stub_container_registry_config(enabled: true) user = create(:user, admin: false) - expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry] + expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry] end it 'contains for admin user all non-default scopes with ADMIN access' do stub_container_registry_config(enabled: true) user = create(:user, admin: true) - expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry sudo] + expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry sudo] end context 'registry_scopes' do @@ -72,7 +72,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do end it 'contains all registry related scopes' do - expect(subject.registry_scopes).to eq %i[read_registry] + expect(subject.registry_scopes).to eq %i[read_registry write_registry] end end end @@ -401,6 +401,49 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do context 'while using deploy tokens' do let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) } + shared_examples 'registry token scope' do + it 'fails when login is not valid' do + expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) + .to eq(auth_failure) + end + + it 'fails when token is not valid' do + expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip')) + .to eq(auth_failure) + end + + it 'fails if token is nil' do + expect(gl_auth.find_for_git_client(login, nil, project: nil, ip: 'ip')) + .to eq(auth_failure) + end + + it 'fails if token is not related to project' do + expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, ip: 'ip')) + .to eq(auth_failure) + end + + it 'fails if token has been revoked' do + deploy_token.revoke! + + expect(deploy_token.revoked?).to be_truthy + expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip')) + .to eq(auth_failure) + end + end + + shared_examples 'deploy token with disabled registry' do + context 'when registry disabled' do + before do + stub_container_registry_config(enabled: false) + end + + it 'fails when login and token are valid' do + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip')) + .to eq(auth_failure) + end + end + end + context 'when deploy token and user have the same username' do let(:username) { 'normal_user' } let(:user) { create(:user, username: username, password: 'my-secret') } @@ -425,34 +468,33 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do context 'and belong to the same project' do let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) } let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) } + let(:auth_success) { Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code]) } it 'succeeds for the right token' do - auth_success = Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code]) - expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip')) .to eq(auth_success) end it 'fails for the wrong token' do expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip')) - .to eq(auth_failure) + .not_to eq(auth_success) end end context 'and belong to different projects' do + let_it_be(:other_project) { create(:project) } let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) } - let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) } + let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [other_project]) } + let(:auth_success) { Gitlab::Auth::Result.new(read_repository, other_project, :deploy_token, [:download_code]) } it 'succeeds for the right token' do - auth_success = Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code]) - - expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip')) + expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: other_project, ip: 'ip')) .to eq(auth_success) end it 'fails for the wrong token' do - expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip')) - .to eq(auth_failure) + expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: other_project, ip: 'ip')) + .not_to eq(auth_success) end end end @@ -542,45 +584,32 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do .to eq(auth_success) end - it 'fails when login is not valid' do - expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) - .to eq(auth_failure) - end + it_behaves_like 'registry token scope' + end - it 'fails when token is not valid' do - expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip')) - .to eq(auth_failure) - end + it_behaves_like 'deploy token with disabled registry' + end - it 'fails if token is nil' do - expect(gl_auth.find_for_git_client(login, nil, project: nil, ip: 'ip')) - .to eq(auth_failure) - end + context 'when the deploy token has write_registry as a scope' do + let_it_be(:deploy_token) { create(:deploy_token, write_registry: true, read_repository: false, read_registry: false, projects: [project]) } + let_it_be(:login) { deploy_token.username } - it 'fails if token is not related to project' do - expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, ip: 'ip')) - .to eq(auth_failure) + context 'when registry enabled' do + before do + stub_container_registry_config(enabled: true) end - it 'fails if token has been revoked' do - deploy_token.revoke! - - expect(deploy_token.revoked?).to be_truthy - expect(gl_auth.find_for_git_client('deploy-token', deploy_token.token, project: nil, ip: 'ip')) - .to eq(auth_failure) - end - end + it 'succeeds when login and a project token are valid' do + auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:create_container_image]) - context 'when registry disabled' do - before do - stub_container_registry_config(enabled: false) + expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip')) + .to eq(auth_success) end - it 'fails when login and token are valid' do - expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip')) - .to eq(auth_failure) - end + it_behaves_like 'registry token scope' end + + it_behaves_like 'deploy token with disabled registry' end end end diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index 568699cf3f6..a2d4c046d46 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -62,7 +62,7 @@ describe DeployToken do context 'with no scopes' do it 'is invalid' do - deploy_token = build(:deploy_token, read_repository: false, read_registry: false) + deploy_token = build(:deploy_token, read_repository: false, read_registry: false, write_registry: false) expect(deploy_token).not_to be_valid expect(deploy_token.errors[:base].first).to eq("Scopes can't be blank") @@ -79,7 +79,7 @@ describe DeployToken do context 'with only one scope' do it 'returns scopes assigned to DeployToken' do - deploy_token = create(:deploy_token, read_registry: false) + deploy_token = create(:deploy_token, read_registry: false, write_registry: false) expect(deploy_token.scopes).to eq([:read_repository]) end end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 84f4a7a4e7a..8273269c2fb 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -766,8 +766,8 @@ describe Auth::ContainerRegistryAuthenticationService do { scopes: ["repository:#{project.full_path}:pull"] } end - context 'when deploy token has read_registry as a scope' do - let(:current_user) { create(:deploy_token, projects: [project]) } + context 'when deploy token has read and write registry as scopes' do + let(:current_user) { create(:deploy_token, write_registry: true, projects: [project]) } shared_examples 'able to login' do context 'registry provides read_container_image authentication_abilities' do @@ -790,7 +790,7 @@ describe Auth::ContainerRegistryAuthenticationService do { scopes: ["repository:#{project.full_path}:push"] } end - it_behaves_like 'an inaccessible' + it_behaves_like 'a pushable' end it_behaves_like 'able to login' @@ -808,7 +808,7 @@ describe Auth::ContainerRegistryAuthenticationService do { scopes: ["repository:#{project.full_path}:push"] } end - it_behaves_like 'an inaccessible' + it_behaves_like 'a pushable' end it_behaves_like 'able to login' @@ -826,7 +826,7 @@ describe Auth::ContainerRegistryAuthenticationService do { scopes: ["repository:#{project.full_path}:push"] } end - it_behaves_like 'an inaccessible' + it_behaves_like 'a pushable' end it_behaves_like 'able to login' diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 443e3dfddf1..c8354f6ba4e 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -311,6 +311,8 @@ describe Projects::ForkService do fork_before_move = fork_project(project) # Stub everything required to move a project to a Gitaly shard that does not exist + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid) stub_storage_settings('test_second_storage' => { 'path' => TestEnv::SECOND_STORAGE_PATH }) allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_repository) .and_return(true) diff --git a/spec/services/projects/update_repository_storage_service_spec.rb b/spec/services/projects/update_repository_storage_service_spec.rb index 23ce6f9165d..05555fa76f7 100644 --- a/spec/services/projects/update_repository_storage_service_spec.rb +++ b/spec/services/projects/update_repository_storage_service_spec.rb @@ -20,6 +20,8 @@ describe Projects::UpdateRepositoryStorageService do let(:project_repository_double) { double(:repository) } before do + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid) allow(Gitlab::Git::Repository).to receive(:new).and_call_original allow(Gitlab::Git::Repository).to receive(:new) .with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path) @@ -49,17 +51,20 @@ describe Projects::UpdateRepositoryStorageService do end end - context 'when the project is already on the target storage' do + context 'when the filesystems are the same' do it 'bails out and does nothing' do result = subject.execute(project.repository_storage) expect(result[:status]).to eq(:error) - expect(result[:message]).to match(/repository and source have the same storage/) + expect(result[:message]).to match(/SameFilesystemError/) end end context 'when the move fails' do it 'unmarks the repository as read-only without updating the repository storage' do + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid) + expect(project_repository_double).to receive(:create_repository) .and_return(true) expect(project_repository_double).to receive(:replicate) @@ -77,6 +82,9 @@ describe Projects::UpdateRepositoryStorageService do context 'when the checksum does not match' do it 'unmarks the repository as read-only without updating the repository storage' do + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid) + expect(project_repository_double).to receive(:create_repository) .and_return(true) expect(project_repository_double).to receive(:replicate) @@ -97,6 +105,9 @@ describe Projects::UpdateRepositoryStorageService do let!(:pool) { create(:pool_repository, :ready, source_project: project) } it 'leaves the pool' do + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid) + expect(project_repository_double).to receive(:create_repository) .and_return(true) expect(project_repository_double).to receive(:replicate) diff --git a/spec/support/services/deploy_token_shared_examples.rb b/spec/support/services/deploy_token_shared_examples.rb index 9d681970739..adc5ea0fcdc 100644 --- a/spec/support/services/deploy_token_shared_examples.rb +++ b/spec/support/services/deploy_token_shared_examples.rb @@ -46,7 +46,7 @@ RSpec.shared_examples 'a deploy token creation service' do end context 'when the deploy token is invalid' do - let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) } + let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false, write_registry: false) } it 'does not create a new DeployToken' do expect { subject }.not_to change { DeployToken.count } diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb index b22379b8b68..d6166ac8188 100644 --- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb +++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb @@ -22,6 +22,9 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| context 'when the move succeeds', :clean_gitlab_redis_shared_state do before do + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid) + allow(project_repository_double).to receive(:create_repository) .and_return(true) allow(project_repository_double).to receive(:replicate) @@ -83,17 +86,19 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| end end - context 'when the project is already on the target storage' do + context 'when the filesystems are the same' do it 'bails out and does nothing' do result = subject.execute(project.repository_storage) expect(result[:status]).to eq(:error) - expect(result[:message]).to match(/repository and source have the same storage/) + expect(result[:message]).to match(/SameFilesystemError/) end end context "when the move of the #{repository_type} repository fails" do it 'unmarks the repository as read-only without updating the repository storage' do + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid) allow(project_repository_double).to receive(:create_repository) .and_return(true) allow(project_repository_double).to receive(:replicate) @@ -119,6 +124,8 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| context "when the checksum of the #{repository_type} repository does not match" do it 'unmarks the repository as read-only without updating the repository storage' do + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original + allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid) allow(project_repository_double).to receive(:create_repository) .and_return(true) allow(project_repository_double).to receive(:replicate) diff --git a/spec/workers/project_update_repository_storage_worker_spec.rb b/spec/workers/project_update_repository_storage_worker_spec.rb index ed99b8135c2..57a4c2128b3 100644 --- a/spec/workers/project_update_repository_storage_worker_spec.rb +++ b/spec/workers/project_update_repository_storage_worker_spec.rb @@ -9,33 +9,12 @@ describe ProjectUpdateRepositoryStorageWorker do subject { described_class.new } describe "#perform" do - context 'when source and target repositories are on different filesystems' do - before do - allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original - allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('new_storage').and_return(SecureRandom.uuid) + it "calls the update repository storage service" do + expect_next_instance_of(Projects::UpdateRepositoryStorageService) do |instance| + expect(instance).to receive(:execute).with('new_storage') end - it "calls the update repository storage service" do - expect_next_instance_of(Projects::UpdateRepositoryStorageService) do |instance| - expect(instance).to receive(:execute).with('new_storage') - end - - subject.perform(project.id, 'new_storage') - end - end - - context 'when source and target repositories are on the same filesystems' do - let(:filesystem_id) { SecureRandom.uuid } - - before do - allow(Gitlab::GitalyClient).to receive(:filesystem_id).and_return(filesystem_id) - end - - it 'raises an error' do - expect_any_instance_of(::Projects::UpdateRepositoryStorageService).not_to receive(:new) - - expect { subject.perform(project.id, 'new_storage') }.to raise_error(ProjectUpdateRepositoryStorageWorker::SameFilesystemError) - end + subject.perform(project.id, 'new_storage') end end end |