Commit 54cd7639 authored by Steve Abrams's avatar Steve Abrams Committed by Andreas Brandl

Add package max file size limits

Add file upload size limits for conan, npm,
nuget, pypi, and maven.
parent 534ef27f
...@@ -7,6 +7,7 @@ module Packages ...@@ -7,6 +7,7 @@ module Packages
def execute def execute
return error('Version is empty.', 400) if version.blank? return error('Version is empty.', 400) if version.blank?
return error('Package already exists.', 403) if current_package_exists? return error('Package already exists.', 403) if current_package_exists?
return error('File is too large.', 400) if file_size_exceeded?
ActiveRecord::Base.transaction { create_package! } ActiveRecord::Base.transaction { create_package! }
end end
...@@ -86,6 +87,10 @@ module Packages ...@@ -86,6 +87,10 @@ module Packages
_version, versions_data = params[:versions].first _version, versions_data = params[:versions].first
versions_data versions_data
end end
def file_size_exceeded?
project.actual_limits.exceeded?(:npm_max_file_size, attachment['length'].to_i)
end
end end
end end
end end
---
title: Add package file size limits to plan limits
merge_request: 39633
author:
type: added
# frozen_string_literal: true
class AddPackageMaxFileSizeToPlanLimits < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column(:plan_limits, :conan_max_file_size, :bigint, default: 50.megabytes, null: false)
add_column(:plan_limits, :maven_max_file_size, :bigint, default: 50.megabytes, null: false)
add_column(:plan_limits, :npm_max_file_size, :bigint, default: 50.megabytes, null: false)
add_column(:plan_limits, :nuget_max_file_size, :bigint, default: 50.megabytes, null: false)
add_column(:plan_limits, :pypi_max_file_size, :bigint, default: 50.megabytes, null: false)
end
end
987f316571f41ad679cad54089bc523f62d04691c10e5cf1957cf60edd71f889
\ No newline at end of file
...@@ -14103,7 +14103,12 @@ CREATE TABLE public.plan_limits ( ...@@ -14103,7 +14103,12 @@ CREATE TABLE public.plan_limits (
ci_max_artifact_size_coverage_fuzzing integer DEFAULT 0 NOT NULL, ci_max_artifact_size_coverage_fuzzing integer DEFAULT 0 NOT NULL,
ci_max_artifact_size_browser_performance integer DEFAULT 0 NOT NULL, ci_max_artifact_size_browser_performance integer DEFAULT 0 NOT NULL,
ci_max_artifact_size_load_performance integer DEFAULT 0 NOT NULL, ci_max_artifact_size_load_performance integer DEFAULT 0 NOT NULL,
ci_needs_size_limit integer DEFAULT 50 NOT NULL ci_needs_size_limit integer DEFAULT 50 NOT NULL,
conan_max_file_size bigint DEFAULT 52428800 NOT NULL,
maven_max_file_size bigint DEFAULT 52428800 NOT NULL,
npm_max_file_size bigint DEFAULT 52428800 NOT NULL,
nuget_max_file_size bigint DEFAULT 52428800 NOT NULL,
pypi_max_file_size bigint DEFAULT 52428800 NOT NULL
); );
CREATE SEQUENCE public.plan_limits_id_seq CREATE SEQUENCE public.plan_limits_id_seq
......
...@@ -514,3 +514,38 @@ Total number of changes (branches or tags) in a single push to determine whether ...@@ -514,3 +514,38 @@ Total number of changes (branches or tags) in a single push to determine whether
individual push events or bulk push event will be created. individual push events or bulk push event will be created.
More information can be found in the [Push event activities limit and bulk push events documentation](../user/admin_area/settings/push_event_activities_limit.md). More information can be found in the [Push event activities limit and bulk push events documentation](../user/admin_area/settings/push_event_activities_limit.md).
## Package Registry Limits
### File Size Limits
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218017) in GitLab 13.4.
On GitLab.com, the maximum file size for a package that's uploaded to the [GitLab Package Registry](../user/packages/package_registry/index.md)
is 50 megabytes.
Limits are set per package type.
To set this limit on a self-managed installation, run the following in the
[GitLab Rails console](troubleshooting/debug.md#starting-a-rails-console-session):
```ruby
# File size limit is stored in bytes
# For Conan Packages
Plan.default.actual_limits.update!(conan_max_file_size: 100.megabytes)
# For NPM Packages
Plan.default.actual_limits.update!(npm_max_file_size: 100.megabytes)
# For NuGet Packages
Plan.default.actual_limits.update!(nuget_max_file_size: 100.megabytes)
# For Maven Packages
Plan.default.actual_limits.update!(maven_max_file_size: 100.megabytes)
# For PyPI Packages
Plan.default.actual_limits.update!(pypi_max_file_size: 100.megabytes)
```
Set the limit to `0` to allow any file size.
...@@ -293,7 +293,7 @@ module API ...@@ -293,7 +293,7 @@ module API
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize' do put 'authorize' do
authorize_workhorse!(subject: project) authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end end
end end
...@@ -320,7 +320,7 @@ module API ...@@ -320,7 +320,7 @@ module API
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
put 'authorize' do put 'authorize' do
authorize_workhorse!(subject: project) authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size)
end end
desc 'Upload package files' do desc 'Upload package files' do
......
...@@ -155,6 +155,7 @@ module API ...@@ -155,6 +155,7 @@ module API
def upload_package_file(file_type) def upload_package_file(file_type)
authorize_upload!(project) authorize_upload!(project)
bad_request!('File is too large') if project.actual_limits.exceeded?(:conan_max_file_size, params['file.size'].to_i)
current_package = find_or_create_package current_package = find_or_create_package
......
...@@ -200,7 +200,7 @@ module API ...@@ -200,7 +200,7 @@ module API
status 200 status 200
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
::Packages::PackageFileUploader.workhorse_authorize(has_length: true) ::Packages::PackageFileUploader.workhorse_authorize(has_length: true, maximum_size: user_project.actual_limits.maven_max_file_size)
end end
desc 'Upload the maven package file' do desc 'Upload the maven package file' do
...@@ -214,6 +214,7 @@ module API ...@@ -214,6 +214,7 @@ module API
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
authorize_upload! authorize_upload!
bad_request!('File is too large') if user_project.actual_limits.exceeded?(:maven_max_file_size, params[:file].size)
file_name, format = extract_format(params[:file_name]) file_name, format = extract_format(params[:file_name])
......
...@@ -92,6 +92,7 @@ module API ...@@ -92,6 +92,7 @@ module API
put do put do
authorize_upload!(authorized_user_project) authorize_upload!(authorized_user_project)
bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size)
file_params = params.merge( file_params = params.merge(
file: params[:package], file: params[:package],
...@@ -118,7 +119,11 @@ module API ...@@ -118,7 +119,11 @@ module API
route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true
put 'authorize' do put 'authorize' do
authorize_workhorse!(subject: authorized_user_project, has_length: false) authorize_workhorse!(
subject: authorized_user_project,
has_length: false,
maximum_size: authorized_user_project.actual_limits.nuget_max_file_size
)
end end
params do params do
......
...@@ -120,6 +120,7 @@ module API ...@@ -120,6 +120,7 @@ module API
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true
post do post do
authorize_upload!(authorized_user_project) authorize_upload!(authorized_user_project)
bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:pypi_max_file_size, params[:content].size)
track_event('push_package') track_event('push_package')
...@@ -136,7 +137,11 @@ module API ...@@ -136,7 +137,11 @@ module API
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true
post 'authorize' do post 'authorize' do
authorize_workhorse!(subject: authorized_user_project, has_length: false) authorize_workhorse!(
subject: authorized_user_project,
has_length: false,
maximum_size: authorized_user_project.actual_limits.pypi_max_file_size
)
end end
end end
end end
......
...@@ -482,4 +482,17 @@ RSpec.describe Packages::Package, type: :model do ...@@ -482,4 +482,17 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to contain_exactly(*tags) } it { is_expected.to contain_exactly(*tags) }
end end
end end
describe 'plan_limits' do
Packages::Package.package_types.keys.without('composer').each do |pt|
context "File size limits for #{pt}" do
let(:package) { create("#{pt}_package") }
it "plan_limits includes column #{pt}_max_file_size" do
expect { package.project.actual_limits.send("#{pt}_max_file_size") }
.not_to raise_error(NoMethodError)
end
end
end
end
end end
...@@ -681,6 +681,18 @@ RSpec.describe API::ConanPackages do ...@@ -681,6 +681,18 @@ RSpec.describe API::ConanPackages do
let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"} let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"}
shared_examples 'uploads a package file' do shared_examples 'uploads a package file' do
context 'file size above maximum limit' do
before do
params['file.size'] = project.actual_limits.conan_max_file_size + 1
end
it 'handles as a local file' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'with object storage disabled' do context 'with object storage disabled' do
context 'without a file from workhorse' do context 'without a file from workhorse' do
let(:params) { { file: nil } } let(:params) { { file: nil } }
......
...@@ -528,6 +528,18 @@ RSpec.describe API::MavenPackages do ...@@ -528,6 +528,18 @@ RSpec.describe API::MavenPackages do
context 'when params from workhorse are correct' do context 'when params from workhorse are correct' do
let(:params) { { file: file_upload } } let(:params) { { file: file_upload } }
context 'file size is too large' do
it 'rejects the request' do
allow_next_instance_of(UploadedFile) do |uploaded_file|
allow(uploaded_file).to receive(:size).and_return(project.actual_limits.maven_max_file_size + 1)
end
upload_file_with_token(params)
expect(response).to have_gitlab_http_status(:bad_request)
end
end
it 'rejects a malicious request' do it 'rejects a malicious request' do
put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2f.ssh%2fauthorized_keys"), params: params, headers: headers_with_token put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2f.ssh%2fauthorized_keys"), params: params, headers: headers_with_token
......
...@@ -220,6 +220,18 @@ RSpec.describe API::NugetPackages do ...@@ -220,6 +220,18 @@ RSpec.describe API::NugetPackages do
it_behaves_like 'rejects nuget access with unknown project id' it_behaves_like 'rejects nuget access with unknown project id'
it_behaves_like 'rejects nuget access with invalid project id' it_behaves_like 'rejects nuget access with invalid project id'
context 'file size above maximum limit' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
before do
allow_next_instance_of(UploadedFile) do |uploaded_file|
allow(uploaded_file).to receive(:size).and_return(project.actual_limits.nuget_max_file_size + 1)
end
end
it_behaves_like 'returning response status', :bad_request
end
end end
end end
......
...@@ -185,6 +185,18 @@ RSpec.describe API::PypiPackages do ...@@ -185,6 +185,18 @@ RSpec.describe API::PypiPackages do
it_behaves_like 'deploy token for package uploads' it_behaves_like 'deploy token for package uploads'
it_behaves_like 'rejects PyPI access with unknown project id' it_behaves_like 'rejects PyPI access with unknown project id'
context 'file size above maximum limit' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) }
before do
allow_next_instance_of(UploadedFile) do |uploaded_file|
allow(uploaded_file).to receive(:size).and_return(project.actual_limits.pypi_max_file_size + 1)
end
end
it_behaves_like 'returning response status', :bad_request
end
end end
describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
......
...@@ -61,6 +61,15 @@ RSpec.describe Packages::Npm::CreatePackageService do ...@@ -61,6 +61,15 @@ RSpec.describe Packages::Npm::CreatePackageService do
it { expect(subject[:message]).to be 'Package already exists.' } it { expect(subject[:message]).to be 'Package already exists.' }
end end
context 'file size above maximum limit' do
before do
params['_attachments']["#{package_name}-#{version}.tgz"]['length'] = project.actual_limits.npm_max_file_size + 1
end
it { expect(subject[:http_status]).to eq 400 }
it { expect(subject[:message]).to be 'File is too large.' }
end
context 'with incorrect namespace' do context 'with incorrect namespace' do
let(:package_name) { '@my_other_namespace/my-app' } let(:package_name) { '@my_other_namespace/my-app' }
......
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