Commit 9398d718 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 602ea426
...@@ -114,7 +114,7 @@ module Groups ...@@ -114,7 +114,7 @@ module Groups
end end
def deploy_token_params 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 end
end end
......
...@@ -93,7 +93,7 @@ module Projects ...@@ -93,7 +93,7 @@ module Projects
end end
def deploy_token_params 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
def run_autodevops_pipeline(service) def run_autodevops_pipeline(service)
......
...@@ -7,7 +7,7 @@ class DeployToken < ApplicationRecord ...@@ -7,7 +7,7 @@ class DeployToken < ApplicationRecord
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
add_authentication_token_field :token, encrypted: :optional 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' GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'
default_value_for(:expires_at) { Forever.date } default_value_for(:expires_at) { Forever.date }
...@@ -105,7 +105,7 @@ class DeployToken < ApplicationRecord ...@@ -105,7 +105,7 @@ class DeployToken < ApplicationRecord
end end
def ensure_at_least_one_scope 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 end
def default_username def default_username
......
...@@ -135,7 +135,7 @@ module Auth ...@@ -135,7 +135,7 @@ module Auth
when 'pull' when 'pull'
build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project) build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project)
when 'push' 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' when 'delete'
build_can_delete?(requested_project) || user_can_admin?(requested_project) build_can_delete?(requested_project) || user_can_admin?(requested_project)
when '*' when '*'
...@@ -185,6 +185,13 @@ module Auth ...@@ -185,6 +185,13 @@ module Auth
current_user.read_registry? current_user.read_registry?
end 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 # We still support legacy pipeline triggers which do not have associated
# actor. New permissions model and new triggers are always associated with # actor. New permissions model and new triggers are always associated with
......
...@@ -5,12 +5,15 @@ module Projects ...@@ -5,12 +5,15 @@ module Projects
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
Error = Class.new(StandardError) Error = Class.new(StandardError)
SameFilesystemError = Class.new(Error)
def initialize(project) def initialize(project)
@project = project @project = project
end end
def execute(new_repository_storage_key) def execute(new_repository_storage_key)
raise SameFilesystemError if same_filesystem?(project.repository.storage, new_repository_storage_key)
mirror_repositories(new_repository_storage_key) mirror_repositories(new_repository_storage_key)
mark_old_paths_for_archive mark_old_paths_for_archive
...@@ -33,6 +36,10 @@ module Projects ...@@ -33,6 +36,10 @@ module Projects
private 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) def mirror_repositories(new_repository_storage_key)
mirror_repository(new_repository_storage_key) mirror_repository(new_repository_storage_key)
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- expanded = expanded_by_default? - expanded = expanded_by_default?
- general_expanded = @project.errors.empty? ? expanded : true - 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) } %section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded) }
.settings-header .settings-header
......
...@@ -30,5 +30,10 @@ ...@@ -30,5 +30,10 @@
= label_tag ("deploy_token_read_registry"), 'read_registry', class: 'label-bold form-check-label' = 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') .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 .prepend-top-default
= f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success qa-create-deploy-token' = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success qa-create-deploy-token'
...@@ -3,21 +3,11 @@ ...@@ -3,21 +3,11 @@
class ProjectUpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker class ProjectUpdateRepositoryStorageWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker include ApplicationWorker
SameFilesystemError = Class.new(StandardError)
feature_category :gitaly feature_category :gitaly
def perform(project_id, new_repository_storage_key) def perform(project_id, new_repository_storage_key)
project = Project.find(project_id) 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) ::Projects::UpdateRepositoryStorageService.new(project).execute(new_repository_storage_key)
end end
private
def same_filesystem?(old_storage, new_storage)
Gitlab::GitalyClient.filesystem_id(old_storage) == Gitlab::GitalyClient.filesystem_id(new_storage)
end
end end
---
title: Add write_registry scope to deploy tokens for container registry push access
merge_request: 28958
author:
type: added
# 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
...@@ -1997,7 +1997,8 @@ CREATE TABLE public.deploy_tokens ( ...@@ -1997,7 +1997,8 @@ CREATE TABLE public.deploy_tokens (
token character varying, token character varying,
username character varying, username character varying,
token_encrypted character varying(255), 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 CREATE SEQUENCE public.deploy_tokens_id_seq
...@@ -13087,6 +13088,7 @@ COPY "schema_migrations" (version) FROM STDIN; ...@@ -13087,6 +13088,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200403185127 20200403185127
20200403185422 20200403185422
20200406135648 20200406135648
20200406192059
20200407094005 20200407094005
20200407094923 20200407094923
20200408110856 20200408110856
......
...@@ -26,6 +26,14 @@ watch [this 1 hour Q&A](https://www.youtube.com/watch?v=uCU8jdYzpac) ...@@ -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 with [John Northrup](https://gitlab.com/northrup), and live questions coming
in from some of our customers. 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 ## High availability
### Omnibus installation with automatic database failover ### Omnibus installation with automatic database failover
......
...@@ -92,7 +92,7 @@ POST /projects/:id/deploy_tokens ...@@ -92,7 +92,7 @@ POST /projects/:id/deploy_tokens
| `name` | string | yes | New deploy token's name | | `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. | | `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}` | | `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 ```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/" 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 ...@@ -193,7 +193,7 @@ POST /groups/:id/deploy_tokens
| `name` | string | yes | New deploy token's name | | `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. | | `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}` | | `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: Example request:
......
...@@ -75,6 +75,10 @@ count as active users in the subscription period in which they were originally a ...@@ -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. - Members with Guest permissions on an Ultimate subscription.
- GitLab-created service accounts: `Ghost User` and `Support Bot`. - 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:** 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. 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.
......
...@@ -463,10 +463,41 @@ The DAST job does not require the project's repository to be present when runnin ...@@ -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 ## 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 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: to the DAST Docker image hosted on your local Docker container registry:
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/17894) in GitLab 10.7. > - [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. > - [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). Deploy tokens can be managed by [maintainers only](../../permissions.md).
...@@ -44,6 +45,7 @@ the following table. ...@@ -44,6 +45,7 @@ the following table.
| ----- | ----------- | | ----- | ----------- |
| `read_repository` | Allows read-access to the repository through `git clone` | | `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. | | `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 ## Deploy token custom username
...@@ -83,6 +85,21 @@ docker login -u <username> -p <deploy_token> registry.example.com ...@@ -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 Just replace `<username>` and `<deploy_token>` with the proper values. Then you can simply
pull images from your Container Registry. 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 ### Group Deploy Token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/21765) in GitLab 12.9. > [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 ...@@ -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 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 automatically exposed to the CI/CD jobs as environment variables: `CI_DEPLOY_USER` and
`CI_DEPLOY_PASSWORD`, respectively. With the GitLab Deploy Token, the `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 After you create the token, you can login to the Container Registry using
those variables: those variables:
......
...@@ -10,6 +10,7 @@ module API ...@@ -10,6 +10,7 @@ module API
result_hash = {} result_hash = {}
result_hash[:read_registry] = scopes.include?('read_registry') 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[:read_repository] = scopes.include?('read_repository')
result_hash result_hash
end end
...@@ -54,7 +55,7 @@ module API ...@@ -54,7 +55,7 @@ module API
params do params do
requires :name, type: String, desc: "New deploy token's name" requires :name, type: String, desc: "New deploy token's name"
requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), 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 :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}`' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`'
end end
...@@ -117,7 +118,7 @@ module API ...@@ -117,7 +118,7 @@ module API
params do params do
requires :name, type: String, desc: 'The name of the deploy token' requires :name, type: String, desc: 'The name of the deploy token'
requires :scopes, type: Array[String], values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), 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 :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}`' optional :username, type: String, desc: 'Username for deploy token. Default is `gitlab+deploy-token-{n}`'
end end
......
...@@ -12,7 +12,7 @@ module Gitlab ...@@ -12,7 +12,7 @@ module Gitlab
REPOSITORY_SCOPES = [:read_repository, :write_repository].freeze REPOSITORY_SCOPES = [:read_repository, :write_repository].freeze
# Scopes used for GitLab Docker Registry access # 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 # Scopes used for GitLab as admin
ADMIN_SCOPES = [:sudo].freeze ADMIN_SCOPES = [:sudo].freeze
...@@ -200,6 +200,7 @@ module Gitlab ...@@ -200,6 +200,7 @@ module Gitlab
api: full_authentication_abilities, api: full_authentication_abilities,
read_api: read_only_authentication_abilities, read_api: read_only_authentication_abilities,
read_registry: [:read_container_image], read_registry: [:read_container_image],
write_registry: [:create_container_image],
read_repository: [:download_code], read_repository: [:download_code],
write_repository: [:download_code, :push_code] write_repository: [:download_code, :push_code]
} }
......
...@@ -6835,6 +6835,9 @@ msgstr "" ...@@ -6835,6 +6835,9 @@ msgstr ""
msgid "DeployTokens|Allows read-only access to the repository" msgid "DeployTokens|Allows read-only access to the repository"
msgstr "" msgstr ""
msgid "DeployTokens|Allows write access to the registry images"
msgstr ""
msgid "DeployTokens|Copy deploy token" msgid "DeployTokens|Copy deploy token"
msgstr "" msgstr ""
...@@ -6853,7 +6856,7 @@ msgstr "" ...@@ -6853,7 +6856,7 @@ msgstr ""
msgid "DeployTokens|Deploy Tokens" msgid "DeployTokens|Deploy Tokens"
msgstr "" 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 "" msgstr ""
msgid "DeployTokens|Expires" msgid "DeployTokens|Expires"
......
...@@ -7,6 +7,7 @@ FactoryBot.define do ...@@ -7,6 +7,7 @@ FactoryBot.define do
sequence(:name) { |n| "PDT #{n}" } sequence(:name) { |n| "PDT #{n}" }
read_repository { true } read_repository { true }
read_registry { true } read_registry { true }
write_registry { false }
revoked { false } revoked { false }
expires_at { 5.days.from_now } expires_at { 5.days.from_now }
deploy_token_type { DeployToken.deploy_token_types[:project_type] } deploy_token_type { DeployToken.deploy_token_types[:project_type] }
......
...@@ -30,7 +30,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -30,7 +30,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'optional_scopes contains all non-default scopes' do it 'optional_scopes contains all non-default scopes' do
stub_container_registry_config(enabled: true) 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
end end
...@@ -38,21 +38,21 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -38,21 +38,21 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'contains all non-default scopes' do it 'contains all non-default scopes' do
stub_container_registry_config(enabled: true) 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 end
it 'contains for non-admin user all non-default scopes without ADMIN access' do it 'contains for non-admin user all non-default scopes without ADMIN access' do
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
user = create(:user, admin: false) 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 end
it 'contains for admin user all non-default scopes with ADMIN access' do it 'contains for admin user all non-default scopes with ADMIN access' do
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
user = create(:user, admin: 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 end
context 'registry_scopes' do context 'registry_scopes' do
...@@ -72,7 +72,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -72,7 +72,7 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
end end
it 'contains all registry related scopes' do 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 end
end end
...@@ -401,6 +401,49 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -401,6 +401,49 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'while using deploy tokens' do context 'while using deploy tokens' do
let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) } 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 context 'when deploy token and user have the same username' do
let(:username) { 'normal_user' } let(:username) { 'normal_user' }
let(:user) { create(:user, username: username, password: 'my-secret') } let(:user) { create(:user, username: username, password: 'my-secret') }
...@@ -425,34 +468,33 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -425,34 +468,33 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
context 'and belong to the same project' do context 'and belong to the same project' do
let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [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: [project]) }
let(:auth_success) { Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code]) }
it 'succeeds for the right token' do 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: project, ip: 'ip'))
.to eq(auth_success) .to eq(auth_success)
end end
it 'fails for the wrong token' do it 'fails for the wrong token' do
expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip')) 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
end end
context 'and belong to different projects' do 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_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 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: other_project, ip: 'ip'))
expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip'))
.to eq(auth_success) .to eq(auth_success)
end end
it 'fails for the wrong token' do it 'fails for the wrong token' do
expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip')) expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: other_project, ip: 'ip'))
.to eq(auth_failure) .not_to eq(auth_success)
end end
end end
end end
...@@ -542,45 +584,32 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -542,45 +584,32 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
.to eq(auth_success) .to eq(auth_success)
end end
it 'fails when login is not valid' do it_behaves_like 'registry token scope'
expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) end
.to eq(auth_failure)
end
it 'fails when token is not valid' do it_behaves_like 'deploy token with disabled registry'
expect(gl_auth.find_for_git_client(login, '123123', project: project, ip: 'ip')) end
.to eq(auth_failure)
end
it 'fails if token is nil' do context 'when the deploy token has write_registry as a scope' do
expect(gl_auth.find_for_git_client(login, nil, project: nil, ip: 'ip')) let_it_be(:deploy_token) { create(:deploy_token, write_registry: true, read_repository: false, read_registry: false, projects: [project]) }
.to eq(auth_failure) let_it_be(:login) { deploy_token.username }
end
it 'fails if token is not related to project' do context 'when registry enabled' do
expect(gl_auth.find_for_git_client(login, 'abcdef', project: nil, ip: 'ip')) before do
.to eq(auth_failure) stub_container_registry_config(enabled: true)
end end
it 'fails if token has been revoked' do it 'succeeds when login and a project token are valid' do
deploy_token.revoke! auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:create_container_image])
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
context 'when registry disabled' do expect(gl_auth.find_for_git_client(login, deploy_token.token, project: project, ip: 'ip'))
before do .to eq(auth_success)
stub_container_registry_config(enabled: false)
end end
it 'fails when login and token are valid' do it_behaves_like 'registry token scope'
expect(gl_auth.find_for_git_client(login, deploy_token.token, project: nil, ip: 'ip'))
.to eq(auth_failure)
end
end end
it_behaves_like 'deploy token with disabled registry'
end end
end end
end end
......
...@@ -62,7 +62,7 @@ describe DeployToken do ...@@ -62,7 +62,7 @@ describe DeployToken do
context 'with no scopes' do context 'with no scopes' do
it 'is invalid' 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).not_to be_valid
expect(deploy_token.errors[:base].first).to eq("Scopes can't be blank") expect(deploy_token.errors[:base].first).to eq("Scopes can't be blank")
...@@ -79,7 +79,7 @@ describe DeployToken do ...@@ -79,7 +79,7 @@ describe DeployToken do
context 'with only one scope' do context 'with only one scope' do
it 'returns scopes assigned to DeployToken' 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]) expect(deploy_token.scopes).to eq([:read_repository])
end end
end end
......
...@@ -766,8 +766,8 @@ describe Auth::ContainerRegistryAuthenticationService do ...@@ -766,8 +766,8 @@ describe Auth::ContainerRegistryAuthenticationService do
{ scopes: ["repository:#{project.full_path}:pull"] } { scopes: ["repository:#{project.full_path}:pull"] }
end end
context 'when deploy token has read_registry as a scope' do context 'when deploy token has read and write registry as scopes' do
let(:current_user) { create(:deploy_token, projects: [project]) } let(:current_user) { create(:deploy_token, write_registry: true, projects: [project]) }
shared_examples 'able to login' do shared_examples 'able to login' do
context 'registry provides read_container_image authentication_abilities' do context 'registry provides read_container_image authentication_abilities' do
...@@ -790,7 +790,7 @@ describe Auth::ContainerRegistryAuthenticationService do ...@@ -790,7 +790,7 @@ describe Auth::ContainerRegistryAuthenticationService do
{ scopes: ["repository:#{project.full_path}:push"] } { scopes: ["repository:#{project.full_path}:push"] }
end end
it_behaves_like 'an inaccessible' it_behaves_like 'a pushable'
end end
it_behaves_like 'able to login' it_behaves_like 'able to login'
...@@ -808,7 +808,7 @@ describe Auth::ContainerRegistryAuthenticationService do ...@@ -808,7 +808,7 @@ describe Auth::ContainerRegistryAuthenticationService do
{ scopes: ["repository:#{project.full_path}:push"] } { scopes: ["repository:#{project.full_path}:push"] }
end end
it_behaves_like 'an inaccessible' it_behaves_like 'a pushable'
end end
it_behaves_like 'able to login' it_behaves_like 'able to login'
...@@ -826,7 +826,7 @@ describe Auth::ContainerRegistryAuthenticationService do ...@@ -826,7 +826,7 @@ describe Auth::ContainerRegistryAuthenticationService do
{ scopes: ["repository:#{project.full_path}:push"] } { scopes: ["repository:#{project.full_path}:push"] }
end end
it_behaves_like 'an inaccessible' it_behaves_like 'a pushable'
end end
it_behaves_like 'able to login' it_behaves_like 'able to login'
......
...@@ -311,6 +311,8 @@ describe Projects::ForkService do ...@@ -311,6 +311,8 @@ describe Projects::ForkService do
fork_before_move = fork_project(project) fork_before_move = fork_project(project)
# Stub everything required to move a project to a Gitaly shard that does not exist # 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 }) stub_storage_settings('test_second_storage' => { 'path' => TestEnv::SECOND_STORAGE_PATH })
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_repository) allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_repository)
.and_return(true) .and_return(true)
......
...@@ -20,6 +20,8 @@ describe Projects::UpdateRepositoryStorageService do ...@@ -20,6 +20,8 @@ describe Projects::UpdateRepositoryStorageService do
let(:project_repository_double) { double(:repository) } let(:project_repository_double) { double(:repository) }
before 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(Gitlab::Git::Repository).to receive(:new).and_call_original allow(Gitlab::Git::Repository).to receive(:new).and_call_original
allow(Gitlab::Git::Repository).to receive(:new) allow(Gitlab::Git::Repository).to receive(:new)
.with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path) .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 ...@@ -49,17 +51,20 @@ describe Projects::UpdateRepositoryStorageService do
end end
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 it 'bails out and does nothing' do
result = subject.execute(project.repository_storage) result = subject.execute(project.repository_storage)
expect(result[:status]).to eq(:error) 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
end end
context 'when the move fails' do context 'when the move fails' do
it 'unmarks the repository as read-only without updating the repository storage' 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) expect(project_repository_double).to receive(:create_repository)
.and_return(true) .and_return(true)
expect(project_repository_double).to receive(:replicate) expect(project_repository_double).to receive(:replicate)
...@@ -77,6 +82,9 @@ describe Projects::UpdateRepositoryStorageService do ...@@ -77,6 +82,9 @@ describe Projects::UpdateRepositoryStorageService do
context 'when the checksum does not match' do context 'when the checksum does not match' do
it 'unmarks the repository as read-only without updating the repository storage' 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) expect(project_repository_double).to receive(:create_repository)
.and_return(true) .and_return(true)
expect(project_repository_double).to receive(:replicate) expect(project_repository_double).to receive(:replicate)
...@@ -97,6 +105,9 @@ describe Projects::UpdateRepositoryStorageService do ...@@ -97,6 +105,9 @@ describe Projects::UpdateRepositoryStorageService do
let!(:pool) { create(:pool_repository, :ready, source_project: project) } let!(:pool) { create(:pool_repository, :ready, source_project: project) }
it 'leaves the pool' do 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) expect(project_repository_double).to receive(:create_repository)
.and_return(true) .and_return(true)
expect(project_repository_double).to receive(:replicate) expect(project_repository_double).to receive(:replicate)
......
...@@ -46,7 +46,7 @@ RSpec.shared_examples 'a deploy token creation service' do ...@@ -46,7 +46,7 @@ RSpec.shared_examples 'a deploy token creation service' do
end end
context 'when the deploy token is invalid' do 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 it 'does not create a new DeployToken' do
expect { subject }.not_to change { DeployToken.count } expect { subject }.not_to change { DeployToken.count }
......
...@@ -22,6 +22,9 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| ...@@ -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 context 'when the move succeeds', :clean_gitlab_redis_shared_state do
before 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) allow(project_repository_double).to receive(:create_repository)
.and_return(true) .and_return(true)
allow(project_repository_double).to receive(:replicate) allow(project_repository_double).to receive(:replicate)
...@@ -83,17 +86,19 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| ...@@ -83,17 +86,19 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
end end
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 it 'bails out and does nothing' do
result = subject.execute(project.repository_storage) result = subject.execute(project.repository_storage)
expect(result[:status]).to eq(:error) 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
end end
context "when the move of the #{repository_type} repository fails" do context "when the move of the #{repository_type} repository fails" do
it 'unmarks the repository as read-only without updating the repository storage' 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) allow(project_repository_double).to receive(:create_repository)
.and_return(true) .and_return(true)
allow(project_repository_double).to receive(:replicate) allow(project_repository_double).to receive(:replicate)
...@@ -119,6 +124,8 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| ...@@ -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 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 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) allow(project_repository_double).to receive(:create_repository)
.and_return(true) .and_return(true)
allow(project_repository_double).to receive(:replicate) allow(project_repository_double).to receive(:replicate)
......
...@@ -9,33 +9,12 @@ describe ProjectUpdateRepositoryStorageWorker do ...@@ -9,33 +9,12 @@ describe ProjectUpdateRepositoryStorageWorker do
subject { described_class.new } subject { described_class.new }
describe "#perform" do describe "#perform" do
context 'when source and target repositories are on different filesystems' do it "calls the update repository storage service" do
before do expect_next_instance_of(Projects::UpdateRepositoryStorageService) do |instance|
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original expect(instance).to receive(:execute).with('new_storage')
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('new_storage').and_return(SecureRandom.uuid)
end end
it "calls the update repository storage service" do subject.perform(project.id, 'new_storage')
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
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment