diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb index d8112f7ef8f295c4d841fb66ede27bb8d9a9d783..2a24084d5e264360d7411490a0acea72de377c12 100644 --- a/app/models/deploy_token.rb +++ b/app/models/deploy_token.rb @@ -8,7 +8,7 @@ class DeployToken < ApplicationRecord AVAILABLE_SCOPES = %i[read_repository read_registry write_registry read_package_registry write_package_registry - read_virtual_registry].freeze + read_virtual_registry write_virtual_registry].freeze GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token' DEPLOY_TOKEN_PREFIX = 'gldt-' diff --git a/db/migrate/20241203052500_add_write_virtual_registry_scope_to_deploy_tokens.rb b/db/migrate/20241203052500_add_write_virtual_registry_scope_to_deploy_tokens.rb new file mode 100644 index 0000000000000000000000000000000000000000..45f780113e6411bf192a5a1dc940ee635f419205 --- /dev/null +++ b/db/migrate/20241203052500_add_write_virtual_registry_scope_to_deploy_tokens.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddWriteVirtualRegistryScopeToDeployTokens < Gitlab::Database::Migration[2.2] + milestone '17.9' + + def change + add_column :deploy_tokens, :write_virtual_registry, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20241203052500 b/db/schema_migrations/20241203052500 new file mode 100644 index 0000000000000000000000000000000000000000..9a4d1ca27ec76807feae3136eca5ae4f5a47d7b8 --- /dev/null +++ b/db/schema_migrations/20241203052500 @@ -0,0 +1 @@ +22a0025bf1ff0bf243698761c17bcfdb181599a4f31ac696c20511dfd1afbf9b \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 4642dc53cd7cbed44c38bc7b9833c4d179eb4e75..ee6b944cb8f828213cd6b2f506cbeb66ee9455a8 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12197,7 +12197,8 @@ CREATE TABLE deploy_tokens ( creator_id bigint, read_virtual_registry boolean DEFAULT false NOT NULL, project_id bigint, - group_id bigint + group_id bigint, + write_virtual_registry boolean DEFAULT false NOT NULL ); CREATE SEQUENCE deploy_tokens_id_seq diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb index 78f55d188bc84dc81ce3df65aa7ded570a90c27c..5440f960ee69587f0682ea70c3fddf2bb417a290 100644 --- a/lib/api/deploy_tokens.rb +++ b/lib/api/deploy_tokens.rb @@ -19,6 +19,8 @@ def scope_params result_hash[:read_package_registry] = scopes.include?('read_package_registry') result_hash[:write_package_registry] = scopes.include?('write_package_registry') result_hash[:read_repository] = scopes.include?('read_repository') + result_hash[:read_virtual_registry] = scopes.include?('read_virtual_registry') + result_hash[:write_virtual_registry] = scopes.include?('write_virtual_registry') result_hash end @@ -90,7 +92,7 @@ def scope_params type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, values: ::DeployToken::AVAILABLE_SCOPES.map(&:to_s), - desc: 'Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, or `write_package_registry`.' + desc: 'Indicates the deploy token scopes. Must be at least one of `read_repository`, `read_registry`, `write_registry`, `read_package_registry`, `write_package_registry`, `read_virtual_registry`, or `write_virtual_registry`.' optional :expires_at, type: DateTime, desc: 'Expiration date for the deploy token. Does not expire if no value is provided. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`).' 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 4fa771b447c7d5f56610ca56577da53cf74b693f..b7dec9bce85fcbd9c57c7b323685550a225058c1 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -46,6 +46,13 @@ module Auth WRITE_REGISTRY_SCOPE = :write_registry REGISTRY_SCOPES = [READ_REGISTRY_SCOPE, WRITE_REGISTRY_SCOPE].freeze + # Scopes used for Virtual Registry access + READ_VIRTUAL_REGISTRY_SCOPE = :read_virtual_registry + WRITE_VIRTUAL_REGISTRY_SCOPE = :write_virtual_registry + VIRTUAL_REGISTRY_SCOPES = [ + READ_VIRTUAL_REGISTRY_SCOPE, WRITE_VIRTUAL_REGISTRY_SCOPE + ].freeze + # Scopes used for GitLab Observability access which is outside of the GitLab app itself. # Hence the lack of ability mapping in `abilities_for_scopes`. READ_OBSERVABILITY_SCOPE = :read_observability @@ -289,6 +296,8 @@ def abilities_for_scopes(scopes) read_api: read_only_authentication_abilities, read_registry: %i[read_container_image], write_registry: %i[create_container_image], + read_virtual_registry: %i[read_dependency_proxy], + write_virtual_registry: %i[write_dependency_proxy], read_repository: %i[download_code], write_repository: %i[download_code push_code], create_runner: %i[create_instance_runner create_runner], @@ -435,6 +444,12 @@ def registry_scopes REGISTRY_SCOPES end + def virtual_registry_scopes + return [] unless Gitlab.config.dependency_proxy.enabled + + VIRTUAL_REGISTRY_SCOPES + end + def resource_bot_scopes non_admin_available_scopes - [READ_USER_SCOPE] end @@ -479,7 +494,7 @@ def unavailable_observability_scopes_for_resource(resource) end def non_admin_available_scopes - API_SCOPES + REPOSITORY_SCOPES + registry_scopes + OBSERVABILITY_SCOPES + AI_FEATURES_SCOPES + API_SCOPES + REPOSITORY_SCOPES + registry_scopes + virtual_registry_scopes + OBSERVABILITY_SCOPES + AI_FEATURES_SCOPES end def find_build_by_token(token) diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb index d0a8a57102f490a193d6881128f77a8dc43ce95c..c53de4d7aebcbae385781c9ea7a910bf9126996c 100644 --- a/spec/factories/deploy_tokens.rb +++ b/spec/factories/deploy_tokens.rb @@ -38,6 +38,7 @@ read_package_registry { true } write_package_registry { true } read_virtual_registry { true } + write_virtual_registry { true } end trait :dependency_proxy_scopes do diff --git a/spec/fixtures/api/schemas/public_api/v4/deploy_token.json b/spec/fixtures/api/schemas/public_api/v4/deploy_token.json index 664740c2a3cf1b4ba056f5ff8ae905af8bd3ccde..a52bae4666e8ccbb14458784920bb76907a37e7d 100644 --- a/spec/fixtures/api/schemas/public_api/v4/deploy_token.json +++ b/spec/fixtures/api/schemas/public_api/v4/deploy_token.json @@ -25,7 +25,15 @@ "scopes": { "type": "array", "items": { - "type": "string" + "enum": [ + "read_repository", + "read_registry", + "write_registry", + "read_package_registry", + "write_package_registry", + "read_virtual_registry", + "write_virtual_registry" + ] } }, "token": { @@ -38,4 +46,4 @@ "type": "boolean" } } -} \ No newline at end of file +} diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index b1df7cf55d2d07b4a97fdf54e78e883b12571a02..06b33d5e9cb50ebe28ec9ccda1babae360b76bb8 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -37,6 +37,10 @@ it 'DEFAULT_SCOPES contains all default scopes' do expect(subject::DEFAULT_SCOPES).to match_array [:api] end + + it 'VIRTUAL_REGISTRY_SCOPES contains all scopes for Virtual Registry access' do + expect(subject::VIRTUAL_REGISTRY_SCOPES).to match_array %i[read_virtual_registry write_virtual_registry] + end end describe 'available_scopes' do @@ -227,6 +231,28 @@ end end end + + context 'virtual_registry_scopes' do + context 'when dependency proxy and virtual registry are both disabled' do + before do + stub_config(dependency_proxy: { enabled: false }) + end + + it 'is empty' do + expect(subject.virtual_registry_scopes).to eq [] + end + end + + context 'when dependency proxy is enabled' do + before do + stub_config(dependency_proxy: { enabled: true }) + end + + it 'contains all virtual registry related scopes' do + expect(subject.virtual_registry_scopes).to eq %i[read_virtual_registry write_virtual_registry] + end + end + end end describe 'find_for_git_client' do diff --git a/spec/requests/api/deploy_tokens_spec.rb b/spec/requests/api/deploy_tokens_spec.rb index 2f215cd5bd131fca372254d751a89311181bd34c..6820c325c9bd53d4879dd4f0549fc880101e8afd 100644 --- a/spec/requests/api/deploy_tokens_spec.rb +++ b/spec/requests/api/deploy_tokens_spec.rb @@ -386,16 +386,38 @@ send(entity).send("add_#{authorized_role}", user) end - it 'creates the deploy token' do - expect { subject }.to change { DeployToken.count }.by(1) - - expect(response).to have_gitlab_http_status(:created) - expect(response).to match_response_schema('public_api/v4/deploy_token') - expect(json_response['name']).to eq('Foo') - expect(json_response['scopes']).to eq(['read_repository']) - expect(json_response['username']).to eq('Bar') - expect(json_response['expires_at'].to_time.to_i).to eq(expires_time.to_i) - expect(json_response['token']).to match(/gldt-[A-Za-z0-9_-]{20}/) + ::DeployToken::AVAILABLE_SCOPES.map(&:to_s).each do |scope| + context "with valid scope #{scope}" do + before do + params[:scopes] = [scope.to_sym] + end + + it 'creates the deploy token' do + expect { subject }.to change { DeployToken.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/deploy_token') + expect(json_response['name']).to eq('Foo') + expect(json_response['scopes']).to eq([scope]) + expect(json_response['username']).to eq('Bar') + expect(json_response['expires_at'].to_time.to_i).to eq(expires_time.to_i) + expect(json_response['token']).to match(/gldt-[A-Za-z0-9_-]{20}/) + end + end + + context 'with all scopes' do + before do + params[:scopes] = ::DeployToken::AVAILABLE_SCOPES + end + + it 'creates the deploy token with all scopes' do + expect { subject }.to change { DeployToken.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/deploy_token') + expect(json_response['scopes']).to eq(::DeployToken::AVAILABLE_SCOPES.map(&:to_s)) + end + end end context 'with no optional params given' do