Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-12-17 03:10:36 +00:00
parent 2e3423047a
commit 6c46be4823
13 changed files with 1134 additions and 1047 deletions

View File

@ -130,6 +130,7 @@ module Auth
ContainerRepository.create_from_path!(path)
end
# Overridden in EE
def can_access?(requested_project, requested_action)
return false unless requested_project.container_registry_enabled?
return false if requested_project.repository_access_level == ::ProjectFeature::DISABLED
@ -226,11 +227,16 @@ module Auth
end
end
# Overridden in EE
def extra_info
{}
end
def log_if_actions_denied(type, requested_project, requested_actions, authorized_actions)
return if requested_actions == authorized_actions
log_info = {
message: "Denied container registry permissions",
message: 'Denied container registry permissions',
scope_type: type,
requested_project_path: requested_project.full_path,
requested_actions: requested_actions,
@ -238,9 +244,11 @@ module Auth
username: current_user&.username,
user_id: current_user&.id,
project_path: project&.full_path
}.compact
}.merge!(extra_info).compact
Gitlab::AuthLogger.warn(log_info)
end
end
end
Auth::ContainerRegistryAuthenticationService.prepend_if_ee('EE::Auth::ContainerRegistryAuthenticationService')

View File

@ -1386,7 +1386,7 @@ test:
storages:
default:
path: tmp/tests/repositories/
gitaly_address: unix:tmp/tests/gitaly/gitaly.socket
gitaly_address: unix:tmp/tests/gitaly/praefect.socket
gitaly:
client_path: tmp/tests/gitaly

View File

@ -13,8 +13,9 @@ ignorecase: true
swap:
active user: '"billable user"'
active users: '"billable users"'
since: '"because" or "after"'
docs: documentation
once that: '"after that"'
once the: '"after the"'
once you: '"after you"'
since: '"because" or "after"'
within: '"in"'

View File

@ -316,6 +316,37 @@ rspec:
- rspec spec
```
If you have jobs that each need a different selection of gems, use the `prefix`
keyword in the global `cache` definition. This configuration generates a different
cache for each job.
For example, a testing job might not need the same gems as a job that deploys to
production:
```yaml
cache:
key:
files:
- Gemfile.lock
prefix: ${CI_JOB_NAME}
paths:
- vendor/ruby
test_job:
stage: test
before_script:
- bundle install --without production --path vendor/ruby
script:
- bundle exec rspec
deploy_job:
stage: production
before_script:
- bundle install --without test --path vendor/ruby
script:
- bundle exec deploy
```
### Caching Go dependencies
Assuming your project is using [Go Modules](https://github.com/golang/go/wiki/Modules) to install

View File

@ -19,14 +19,14 @@ in your existing `.gitlab-ci.yml` file or by implicitly using
[Auto License Compliance](../../../topics/autodevops/stages.md#auto-license-compliance)
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
GitLab checks the License Compliance report, compares the licenses between the
source and target branches, and shows the information right on the merge request.
Denied licenses are notated with an `x` red icon next to them
as well as new licenses which need a decision from you. In addition, you can
[manually allow or deny](#policies)
licenses in your project's license compliance policy section. If GitLab detects a denied license
in a new commit, GitLab blocks any merge requests containing that commit and instructs the developer
to remove the license.
The [License Finder](https://github.com/pivotal/LicenseFinder) scan tool runs as part of the CI/CD
pipeline, and detects the licenses in use. GitLab checks the License Compliance report, compares the
licenses between the source and target branches, and shows the information right on the merge
request. Denied licenses are indicated by a `x` red icon next to them as well as new licenses that
need a decision from you. In addition, you can [manually allow or deny](#policies) licenses in your
project's license compliance policy section. If a denied license is detected in a new commit,
GitLab blocks any merge requests containing that commit and instructs the developer to remove the
license.
NOTE:
If the license compliance report doesn't have anything to compare to, no information
@ -51,36 +51,33 @@ You can view and modify existing policies from the [policies](#policies) tab.
The following languages and package managers are supported.
| Language | Package managers | Notes | Scan Tool |
|------------|------------------|-------|-----------|
| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/) | | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Go | [Godep](https://github.com/tools/godep), [go mod](https://github.com/golang/go/wiki/Modules) | | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) | | [License Finder](https://github.com/pivotal/LicenseFinder) |
| .NET | [Nuget](https://www.nuget.org/) | The .NET Framework is supported via the [mono project](https://www.mono-project.com/). There are, however, some limitations. The scanner doesn't support Windows-specific dependencies and doesn't report dependencies of your project's listed dependencies. Also, the scanner always marks detected licenses for all dependencies as `unknown`. | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Python | [pip](https://pip.pypa.io/en/stable/) | Python is supported through [requirements.txt](https://pip.pypa.io/en/stable/user_guide/#requirements-files) and [Pipfile.lock](https://github.com/pypa/pipfile#pipfilelock). | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Ruby | [gem](https://rubygems.org/) | | [License Finder](https://github.com/pivotal/LicenseFinder)|
Java 8 and Gradle 1.x projects are not supported. The minimum supported version of Maven is 3.2.5.
NOTE:
Java 8 and Gradle 1.x projects are not supported.
The minimum supported version of Maven is 3.2.5.
| Language | Package managers | Notes |
|------------|----------------------------------------------------------------------------------------------|-------|
| JavaScript | [Bower](https://bower.io/), [npm](https://www.npmjs.com/) | |
| Go | [Godep](https://github.com/tools/godep), [go mod](https://github.com/golang/go/wiki/Modules) | |
| Java | [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) | |
| .NET | [Nuget](https://www.nuget.org/) | The .NET Framework is supported via the [mono project](https://www.mono-project.com/). There are, however, some limitations. The scanner doesn't support Windows-specific dependencies and doesn't report dependencies of your project's listed dependencies. Also, the scanner always marks detected licenses for all dependencies as `unknown`. |
| Python | [pip](https://pip.pypa.io/en/stable/) | Python is supported through [requirements.txt](https://pip.pypa.io/en/stable/user_guide/#requirements-files) and [Pipfile.lock](https://github.com/pypa/pipfile#pipfilelock). |
| Ruby | [gem](https://rubygems.org/) | |
### Experimental support
The following languages and package managers are [supported experimentally](https://github.com/pivotal/LicenseFinder#experimental-project-types),
which means that the reported licenses might be incomplete or inaccurate.
The following languages and package managers are [supported experimentally](https://github.com/pivotal/LicenseFinder#experimental-project-types).
The reported licenses might be incomplete or inaccurate.
| Language | Package managers | Scan Tool |
|------------|-------------------------------------------------------------------|----------------------------------------------------------|
| JavaScript | [Yarn](https://yarnpkg.com/)|[License Finder](https://github.com/pivotal/LicenseFinder)|
| Go | go get, gvt, glide, dep, trash, govendor |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Erlang | [Rebar](https://www.rebar3.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Objective-C, Swift | [CocoaPods](https://cocoapods.org/) v0.39 and below |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Elixir | [Mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| C++/C | [Conan](https://conan.io/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Scala | [sbt](https://www.scala-sbt.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Rust | [Cargo](https://crates.io) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| PHP | [Composer](https://getcomposer.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Language | Package managers |
|------------|---------------------------------------------------------------------------------------------------------------|
| JavaScript | [Yarn](https://yarnpkg.com/) |
| Go | go get, gvt, glide, dep, trash, govendor |
| Erlang | [Rebar](https://www.rebar3.org/) |
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage), [CocoaPods](https://cocoapods.org/) v0.39 and below |
| Elixir | [Mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) |
| C++/C | [Conan](https://conan.io/) |
| Scala | [sbt](https://www.scala-sbt.org/) |
| Rust | [Cargo](https://crates.io) |
| PHP | [Composer](https://getcomposer.org/) |
## Requirements

View File

@ -288,7 +288,7 @@ below.
WARNING:
Interactive Web Terminals for the Web IDE is currently in **Beta**.
Shared runners [do not yet support Interactive Web Terminals](https://gitlab.com/gitlab-org/gitlab/-/issues/24674),
GitLab.com shared runners [do not yet support Interactive Web Terminals](https://gitlab.com/gitlab-org/gitlab/-/issues/24674),
so you would need to use your own private runner to make use of this feature.
[Interactive Web Terminals](../../../ci/interactive_web_terminal/index.md)

View File

@ -4,10 +4,10 @@ require 'toml-rb'
module Gitlab
module SetupHelper
def create_configuration(dir, storage_paths, force: false)
def create_configuration(dir, storage_paths, force: false, options: {})
generate_configuration(
configuration_toml(dir, storage_paths),
get_config_path(dir),
configuration_toml(dir, storage_paths, options),
get_config_path(dir, options),
force: force
)
end
@ -31,7 +31,7 @@ module Gitlab
module Workhorse
extend Gitlab::SetupHelper
class << self
def configuration_toml(dir, _)
def configuration_toml(dir, _, _)
config = { redis: { URL: redis_url } }
TomlRB.dump(config)
@ -41,8 +41,8 @@ module Gitlab
Gitlab::Redis::SharedState.url
end
def get_config_path(dir)
File.join(dir, 'config.toml')
def get_config_path(dir, _)
File.join(dir, 'config_path')
end
def compile_into(dir)
@ -76,7 +76,7 @@ module Gitlab
# because it uses a Unix socket.
# For development and testing purposes, an extra storage is added to gitaly,
# which is not known to Rails, but must be explicitly stubbed.
def configuration_toml(gitaly_dir, storage_paths, gitaly_ruby: true)
def configuration_toml(gitaly_dir, storage_paths, options, gitaly_ruby: true)
storages = []
address = nil
@ -97,14 +97,20 @@ module Gitlab
config = { socket_path: address.sub(/\Aunix:/, '') }
if Rails.env.test?
socket_filename = options[:gitaly_socket] || "gitaly.socket"
config = {
# Override the set gitaly_address since Praefect is in the loop
socket_path: File.join(gitaly_dir, socket_filename),
auth: { token: 'secret' },
# Compared to production, tests run in constrained environments. This
# number is meant to grow with the number of concurrent rails requests /
# sidekiq jobs, and concurrency will be low anyway in test.
git: { catfile_cache_size: 5 }
}
storage_path = Rails.root.join('tmp', 'tests', 'second_storage').to_s
storages << { name: 'test_second_storage', path: storage_path }
config[:auth] = { token: 'secret' }
# Compared to production, tests run in constrained environments. This
# number is meant to grow with the number of concurrent rails requests /
# sidekiq jobs, and concurrency will be low anyway in test.
config[:git] = { catfile_cache_size: 5 }
end
config[:storage] = storages
@ -124,8 +130,9 @@ module Gitlab
private
def get_config_path(dir)
File.join(dir, 'config.toml')
def get_config_path(dir, options)
config_filename = options[:config_filename] || 'config.toml'
File.join(dir, config_filename)
end
end
end
@ -133,9 +140,11 @@ module Gitlab
module Praefect
extend Gitlab::SetupHelper
class << self
def configuration_toml(gitaly_dir, storage_paths)
def configuration_toml(gitaly_dir, _, _)
nodes = [{ storage: 'default', address: "unix:#{gitaly_dir}/gitaly.socket", primary: true, token: 'secret' }]
storages = [{ name: 'default', node: nodes }]
second_storage_nodes = [{ storage: 'test_second_storage', address: "unix:#{gitaly_dir}/gitaly2.socket", primary: true, token: 'secret' }]
storages = [{ name: 'default', node: nodes }, { name: 'test_second_storage', node: second_storage_nodes }]
failover = { enabled: false }
config = { socket_path: "#{gitaly_dir}/praefect.socket", memory_queue_enabled: true, virtual_storage: storages, failover: failover }
config[:token] = 'secret' if Rails.env.test?
@ -145,7 +154,7 @@ module Gitlab
private
def get_config_path(dir)
def get_config_path(dir, _)
File.join(dir, 'praefect.config.toml')
end
end

View File

@ -19,8 +19,10 @@ class GitalyTestBuild
# Starting gitaly further validates its configuration
gitaly_pid = start_gitaly
gitaly2_pid = start_gitaly2
praefect_pid = start_praefect
Process.kill('TERM', gitaly_pid)
Process.kill('TERM', gitaly2_pid)
Process.kill('TERM', praefect_pid)
# Make the 'gitaly' executable look newer than 'GITALY_SERVER_VERSION'.

View File

@ -15,6 +15,7 @@ class GitalyTestSpawn
# In local development this pid file is used by rspec.
IO.write(File.expand_path('../tmp/tests/gitaly.pid', __dir__), start_gitaly)
IO.write(File.expand_path('../tmp/tests/gitaly2.pid', __dir__), start_gitaly2)
IO.write(File.expand_path('../tmp/tests/praefect.pid', __dir__), start_praefect)
end
end

View File

@ -62,21 +62,36 @@ module GitalyTest
case service
when :gitaly
File.join(tmp_tests_gitaly_dir, 'config.toml')
when :gitaly2
File.join(tmp_tests_gitaly_dir, 'gitaly2.config.toml')
when :praefect
File.join(tmp_tests_gitaly_dir, 'praefect.config.toml')
end
end
def service_binary(service)
case service
when :gitaly, :gitaly2
'gitaly'
when :praefect
'praefect'
end
end
def start_gitaly
start(:gitaly)
end
def start_gitaly2
start(:gitaly2)
end
def start_praefect
start(:praefect)
end
def start(service)
args = ["#{tmp_tests_gitaly_dir}/#{service}"]
args = ["#{tmp_tests_gitaly_dir}/#{service_binary(service)}"]
args.push("-config") if service == :praefect
args.push(config_path(service))
pid = spawn(env, *args, [:out, :err] => "log/#{service}-test.log")

View File

@ -5,993 +5,5 @@ require 'spec_helper'
RSpec.describe Auth::ContainerRegistryAuthenticationService do
include AdminModeHelper
let(:current_project) { nil }
let(:current_user) { nil }
let(:current_params) { {} }
let(:rsa_key) { OpenSSL::PKey::RSA.generate(512) }
let(:payload) { JWT.decode(subject[:token], rsa_key, true, { algorithm: 'RS256' }).first }
let(:authentication_abilities) do
[:read_container_image, :create_container_image, :admin_container_image]
end
subject do
described_class.new(current_project, current_user, current_params)
.execute(authentication_abilities: authentication_abilities)
end
before do
allow(Gitlab.config.registry).to receive_messages(enabled: true, issuer: 'rspec', key: nil)
allow_next_instance_of(JSONWebToken::RSAToken) do |instance|
allow(instance).to receive(:key).and_return(rsa_key)
end
end
shared_examples 'an authenticated' do
it { is_expected.to include(:token) }
it { expect(payload).to include('access') }
end
shared_examples 'a valid token' do
it { is_expected.to include(:token) }
it { expect(payload).to include('access') }
context 'a expirable' do
let(:expires_at) { Time.zone.at(payload['exp']) }
let(:expire_delay) { 10 }
context 'for default configuration' do
it { expect(expires_at).not_to be_within(2.seconds).of(Time.current + expire_delay.minutes) }
end
context 'for changed configuration' do
before do
stub_application_setting(container_registry_token_expire_delay: expire_delay)
end
it { expect(expires_at).to be_within(2.seconds).of(Time.current + expire_delay.minutes) }
end
end
end
shared_examples 'a browsable' do
let(:access) do
[{ 'type' => 'registry',
'name' => 'catalog',
'actions' => ['*'] }]
end
it_behaves_like 'a valid token'
it_behaves_like 'not a container repository factory'
it 'has the correct scope' do
expect(payload).to include('access' => access)
end
end
shared_examples 'an accessible' do
let(:access) do
[{ 'type' => 'repository',
'name' => project.full_path,
'actions' => actions }]
end
it_behaves_like 'a valid token'
it 'has the correct scope' do
expect(payload).to include('access' => access)
end
end
shared_examples 'an inaccessible' do
it_behaves_like 'a valid token'
it { expect(payload).to include('access' => []) }
end
shared_examples 'a deletable' do
it_behaves_like 'an accessible' do
let(:actions) { ['*'] }
end
end
shared_examples 'a deletable since registry 2.7' do
it_behaves_like 'an accessible' do
let(:actions) { ['delete'] }
end
end
shared_examples 'a pullable' do
it_behaves_like 'an accessible' do
let(:actions) { ['pull'] }
end
end
shared_examples 'a pushable' do
it_behaves_like 'an accessible' do
let(:actions) { ['push'] }
end
end
shared_examples 'a pullable and pushable' do
it_behaves_like 'an accessible' do
let(:actions) { %w(pull push) }
end
end
shared_examples 'a forbidden' do
it { is_expected.to include(http_status: 403) }
it { is_expected.not_to include(:token) }
end
shared_examples 'container repository factory' do
it 'creates a new container repository resource' do
expect { subject }
.to change { project.container_repositories.count }.by(1)
end
end
shared_examples 'not a container repository factory' do
it 'does not create a new container repository resource' do
expect { subject }.not_to change { ContainerRepository.count }
end
end
describe '#full_access_token' do
let_it_be(:project) { create(:project) }
let(:token) { described_class.full_access_token(project.full_path) }
subject { { token: token } }
it_behaves_like 'an accessible' do
let(:actions) { ['*'] }
end
it_behaves_like 'not a container repository factory'
end
describe '#pull_access_token' do
let_it_be(:project) { create(:project) }
let(:token) { described_class.pull_access_token(project.full_path) }
subject { { token: token } }
it_behaves_like 'an accessible' do
let(:actions) { ['pull'] }
end
it_behaves_like 'not a container repository factory'
end
context 'user authorization' do
let_it_be(:current_user) { create(:user) }
context 'for registry catalog' do
let(:current_params) do
{ scopes: ["registry:catalog:*"] }
end
context 'disallow browsing for users without GitLab admin rights' do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
end
context 'for private project' do
let_it_be(:project) { create(:project) }
context 'allow to use scope-less authentication' do
it_behaves_like 'a valid token'
end
context 'allow developer to push images' do
before_all do
project.add_developer(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'a pushable'
it_behaves_like 'container repository factory'
end
context 'disallow developer to delete images' do
before_all do
project.add_developer(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:*"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
it 'logs an auth warning' do
expect(Gitlab::AuthLogger).to receive(:warn).with(
message: 'Denied container registry permissions',
scope_type: 'repository',
requested_project_path: project.full_path,
requested_actions: ['*'],
authorized_actions: [],
user_id: current_user.id,
username: current_user.username
)
subject
end
end
context 'disallow developer to delete images since registry 2.7' do
before_all do
project.add_developer(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:delete"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'allow reporter to pull images' do
before_all do
project.add_reporter(current_user)
end
context 'when pulling from root level repository' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
end
context 'disallow reporter to delete images' do
before_all do
project.add_reporter(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:*"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow reporter to delete images since registry 2.7' do
before_all do
project.add_reporter(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:delete"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'return a least of privileges' do
before_all do
project.add_reporter(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push,pull"] }
end
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
context 'disallow guest to pull or push images' do
before_all do
project.add_guest(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull,push"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow guest to delete images' do
before_all do
project.add_guest(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:*"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow guest to delete images since registry 2.7' do
before_all do
project.add_guest(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:delete"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
end
context 'for public project' do
let_it_be(:project) { create(:project, :public) }
context 'allow anyone to pull images' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
context 'disallow anyone to push images' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow anyone to delete images' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:*"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow anyone to delete images since registry 2.7' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:delete"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'when repository name is invalid' do
let(:current_params) do
{ scopes: ['repository:invalid:push'] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
end
context 'for internal project' do
let_it_be(:project) { create(:project, :internal) }
context 'for internal user' do
context 'allow anyone to pull images' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
context 'disallow anyone to push images' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow anyone to delete images' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:*"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow anyone to delete images since registry 2.7' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:delete"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
end
context 'for external user' do
context 'disallow anyone to pull or push images' do
let_it_be(:current_user) { create(:user, external: true) }
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull,push"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow anyone to delete images' do
let_it_be(:current_user) { create(:user, external: true) }
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:*"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'disallow anyone to delete images since registry 2.7' do
let_it_be(:current_user) { create(:user, external: true) }
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:delete"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
end
end
end
context 'delete authorized as maintainer' do
let_it_be(:current_project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let(:authentication_abilities) do
[:admin_container_image]
end
before_all do
current_project.add_maintainer(current_user)
end
it_behaves_like 'a valid token'
context 'allow to delete images' do
let(:current_params) do
{ scopes: ["repository:#{current_project.full_path}:*"] }
end
it_behaves_like 'a deletable' do
let(:project) { current_project }
end
end
context 'allow to delete images since registry 2.7' do
let(:current_params) do
{ scopes: ["repository:#{current_project.full_path}:delete"] }
end
it_behaves_like 'a deletable since registry 2.7' do
let(:project) { current_project }
end
end
end
context 'build authorized as user' do
let_it_be(:current_project) { create(:project) }
let_it_be(:current_user) { create(:user) }
let(:authentication_abilities) do
[:build_read_container_image, :build_create_container_image, :build_destroy_container_image]
end
before_all do
current_project.add_developer(current_user)
end
context 'allow to use offline_token' do
let(:current_params) do
{ offline_token: true }
end
it_behaves_like 'an authenticated'
end
it_behaves_like 'a valid token'
context 'allow to pull and push images' do
let(:current_params) do
{ scopes: ["repository:#{current_project.full_path}:pull,push"] }
end
it_behaves_like 'a pullable and pushable' do
let(:project) { current_project }
end
it_behaves_like 'container repository factory' do
let(:project) { current_project }
end
end
context 'allow to delete images since registry 2.7' do
let(:current_params) do
{ scopes: ["repository:#{current_project.full_path}:delete"] }
end
it_behaves_like 'a deletable since registry 2.7' do
let(:project) { current_project }
end
end
context 'disallow to delete images' do
let(:current_params) do
{ scopes: ["repository:#{current_project.full_path}:*"] }
end
it_behaves_like 'an inaccessible' do
let(:project) { current_project }
end
end
context 'for other projects' do
context 'when pulling' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
context 'allow for public' do
let_it_be(:project) { create(:project, :public) }
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
shared_examples 'pullable for being team member' do
context 'when you are not member' do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'when you are member' do
before_all do
project.add_developer(current_user)
end
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
context 'when you are owner' do
let_it_be(:project) { create(:project, namespace: current_user.namespace) }
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
end
context 'for private' do
let_it_be(:project) { create(:project, :private) }
it_behaves_like 'pullable for being team member'
context 'when you are admin' do
let_it_be(:current_user) { create(:admin) }
context 'when you are not member' do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'when you are member' do
before_all do
project.add_developer(current_user)
end
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
context 'when you are owner' do
let_it_be(:project) { create(:project, namespace: current_user.namespace) }
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
end
end
end
context 'when pushing' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
context 'disallow for all' do
context 'when you are member' do
let_it_be(:project) { create(:project, :public) }
before_all do
project.add_developer(current_user)
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
context 'when you are owner' do
let_it_be(:project) { create(:project, :public, namespace: current_user.namespace) }
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
end
end
end
context 'for project without container registry' do
let_it_be(:project) { create(:project, :public, container_registry_enabled: false) }
before do
project.update!(container_registry_enabled: false)
end
context 'disallow when pulling' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
end
context 'for project that disables repository' do
let_it_be(:project) { create(:project, :public, :repository_disabled) }
context 'disallow when pulling' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
end
end
context 'registry catalog browsing authorized as admin' do
let_it_be(:current_user) { create(:user, :admin) }
let_it_be(:project) { create(:project, :public) }
let(:current_params) do
{ scopes: ["registry:catalog:*"] }
end
it_behaves_like 'a browsable'
end
context 'support for multiple scopes' do
let_it_be(:internal_project) { create(:project, :internal) }
let_it_be(:private_project) { create(:project, :private) }
let(:current_params) do
{
scopes: [
"repository:#{internal_project.full_path}:pull",
"repository:#{private_project.full_path}:pull"
]
}
end
context 'user has access to all projects' do
let_it_be(:current_user) { create(:user, :admin) }
before do
enable_admin_mode!(current_user)
end
it_behaves_like 'a browsable' do
let(:access) do
[
{ 'type' => 'repository',
'name' => internal_project.full_path,
'actions' => ['pull'] },
{ 'type' => 'repository',
'name' => private_project.full_path,
'actions' => ['pull'] }
]
end
end
end
context 'user only has access to internal project' do
let_it_be(:current_user) { create(:user) }
it_behaves_like 'a browsable' do
let(:access) do
[
{ 'type' => 'repository',
'name' => internal_project.full_path,
'actions' => ['pull'] }
]
end
end
end
context 'anonymous access is rejected' do
let(:current_user) { nil }
it_behaves_like 'a forbidden'
end
end
context 'unauthorized' do
context 'disallow to use scope-less authentication' do
it_behaves_like 'a forbidden'
it_behaves_like 'not a container repository factory'
end
context 'for invalid scope' do
let(:current_params) do
{ scopes: ['invalid:aa:bb'] }
end
it_behaves_like 'a forbidden'
it_behaves_like 'not a container repository factory'
end
context 'for private project' do
let_it_be(:project) { create(:project, :private) }
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
it_behaves_like 'a forbidden'
end
context 'for public project' do
let_it_be(:project) { create(:project, :public) }
context 'when pulling and pushing' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull,push"] }
end
it_behaves_like 'a pullable'
it_behaves_like 'not a container repository factory'
end
context 'when pushing' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'a forbidden'
it_behaves_like 'not a container repository factory'
end
end
context 'for registry catalog' do
let(:current_params) do
{ scopes: ["registry:catalog:*"] }
end
it_behaves_like 'a forbidden'
it_behaves_like 'not a container repository factory'
end
end
context 'for deploy tokens' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:pull"] }
end
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
let(:current_params) { {} }
let(:authentication_abilities) { [:read_container_image] }
it_behaves_like 'an authenticated'
end
end
context 'for public project' do
let_it_be(:project) { create(:project, :public) }
context 'when pulling' do
it_behaves_like 'a pullable'
end
context 'when pushing' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'a pushable'
end
it_behaves_like 'able to login'
end
context 'for internal project' do
let_it_be(:project) { create(:project, :internal) }
context 'when pulling' do
it_behaves_like 'a pullable'
end
context 'when pushing' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'a pushable'
end
it_behaves_like 'able to login'
end
context 'for private project' do
let_it_be(:project) { create(:project, :private) }
context 'when pulling' do
it_behaves_like 'a pullable'
end
context 'when pushing' do
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'a pushable'
end
it_behaves_like 'able to login'
end
end
context 'when deploy token does not have read_registry scope' do
let(:current_user) { create(:deploy_token, projects: [project], read_registry: false) }
shared_examples 'unable to login' do
context 'registry provides no container authentication_abilities' do
let(:current_params) { {} }
let(:authentication_abilities) { [] }
it_behaves_like 'a forbidden'
end
context 'registry provides inapplicable container authentication_abilities' do
let(:current_params) { {} }
let(:authentication_abilities) { [:download_code] }
it_behaves_like 'a forbidden'
end
end
context 'for public project' do
let_it_be(:project) { create(:project, :public) }
context 'when pulling' do
it_behaves_like 'a pullable'
end
it_behaves_like 'unable to login'
end
context 'for internal project' do
let_it_be(:project) { create(:project, :internal) }
context 'when pulling' do
it_behaves_like 'an inaccessible'
end
it_behaves_like 'unable to login'
end
context 'for private project' do
let_it_be(:project) { create(:project, :internal) }
context 'when pulling' do
it_behaves_like 'an inaccessible'
end
context 'when logging in' do
let(:current_params) { {} }
let(:authentication_abilities) { [] }
it_behaves_like 'a forbidden'
end
it_behaves_like 'unable to login'
end
end
context 'when deploy token is not related to the project' do
let_it_be(:current_user) { create(:deploy_token, read_registry: false) }
context 'for public project' do
let_it_be(:project) { create(:project, :public) }
context 'when pulling' do
it_behaves_like 'a pullable'
end
end
context 'for internal project' do
let_it_be(:project) { create(:project, :internal) }
context 'when pulling' do
it_behaves_like 'an inaccessible'
end
end
context 'for private project' do
let_it_be(:project) { create(:project, :internal) }
context 'when pulling' do
it_behaves_like 'an inaccessible'
end
end
end
context 'when deploy token has been revoked' do
let(:current_user) { create(:deploy_token, :revoked, projects: [project]) }
context 'for public project' do
let_it_be(:project) { create(:project, :public) }
it_behaves_like 'a pullable'
end
context 'for internal project' do
let_it_be(:project) { create(:project, :internal) }
it_behaves_like 'an inaccessible'
end
context 'for private project' do
let_it_be(:project) { create(:project, :internal) }
it_behaves_like 'an inaccessible'
end
end
end
context 'user authorization' do
let_it_be(:current_user) { create(:user) }
context 'with multiple scopes' do
let_it_be(:project) { create(:project) }
context 'allow developer to push images' do
before_all do
project.add_developer(current_user)
end
let(:current_params) do
{ scopes: ["repository:#{project.full_path}:push"] }
end
it_behaves_like 'a pushable'
it_behaves_like 'container repository factory'
end
end
end
it_behaves_like 'a container registry auth service'
end

View File

@ -168,6 +168,11 @@ module TestEnv
version: Gitlab::GitalyClient.expected_server_version,
task: "gitlab:gitaly:install[#{install_gitaly_args}]") do
Gitlab::SetupHelper::Gitaly.create_configuration(gitaly_dir, { 'default' => repos_path }, force: true)
Gitlab::SetupHelper::Gitaly.create_configuration(
gitaly_dir,
{ 'default' => repos_path }, force: true,
options: { gitaly_socket: "gitaly2.socket", config_filename: "gitaly2.config.toml" }
)
Gitlab::SetupHelper::Praefect.create_configuration(gitaly_dir, { 'praefect' => repos_path }, force: true)
end
@ -283,7 +288,7 @@ module TestEnv
host = "[#{host}]" if host.include?(':')
listen_addr = [host, port].join(':')
config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir)
config_path = Gitlab::SetupHelper::Workhorse.get_config_path(workhorse_dir, {})
# This should be set up in setup_workhorse, but since
# component_needs_update? only checks that versions are consistent,