Commit 760c9334 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch '32102-fix-maven-head-redirects' into 'master'

Allow HEAD API requests for Maven packages with AWS as object storage

See merge request gitlab-org/gitlab!27612
parents f494909e be5fa52a
...@@ -119,9 +119,6 @@ upload packages: ...@@ -119,9 +119,6 @@ upload packages:
} }
``` ```
NOTE: **Note:**
Some build tools, like Gradle, must make `HEAD` requests to Amazon S3 to pull a dependency’s metadata. The `gitlab_rails['packages_object_store_proxy_download']` property must be set to `true`. Without this setting, GitLab won't act as a proxy to the Amazon S3 service, and will instead return the signed URL. This will cause a `HTTP 403 Forbidden` response, since Amazon S3 expects a signed URL.
1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) 1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure)
for the changes to take effect. for the changes to take effect.
......
---
title: Maven packages API allows HEAD requests to package files when using Amazon
S3 as a object storage backend
merge_request: 27612
author:
type: fixed
...@@ -50,6 +50,31 @@ module API ...@@ -50,6 +50,31 @@ module API
def jar_file?(format) def jar_file?(format)
format == 'jar' format == 'jar'
end end
def present_carrierwave_file_with_head_support!(file, supports_direct_download: true)
if head_request_on_aws_file?(file, supports_direct_download) && !file.file_storage?
return redirect(signed_head_url(file))
end
present_carrierwave_file!(file, supports_direct_download: supports_direct_download)
end
def signed_head_url(file)
fog_storage = ::Fog::Storage.new(file.fog_credentials)
fog_dir = fog_storage.directories.new(key: file.fog_directory)
fog_file = fog_dir.files.new(key: file.path)
expire_at = ::Fog::Time.now + file.fog_authenticated_url_expiration
fog_file.collection.head_url(fog_file.key, expire_at)
end
def head_request_on_aws_file?(file, supports_direct_download)
Gitlab.config.packages.object_store.enabled &&
supports_direct_download &&
file.class.direct_download_enabled? &&
request.head? &&
file.fog_credentials[:provider] == 'AWS'
end
end end
desc 'Download the maven package file at instance level' do desc 'Download the maven package file at instance level' do
...@@ -85,8 +110,7 @@ module API ...@@ -85,8 +110,7 @@ module API
package_file.file_sha1 package_file.file_sha1
else else
track_event('pull_package') if jar_file?(format) track_event('pull_package') if jar_file?(format)
present_carrierwave_file_with_head_support!(package_file.file)
present_carrierwave_file!(package_file.file)
end end
end end
...@@ -126,7 +150,7 @@ module API ...@@ -126,7 +150,7 @@ module API
else else
track_event('pull_package') if jar_file?(format) track_event('pull_package') if jar_file?(format)
present_carrierwave_file!(package_file.file) present_carrierwave_file_with_head_support!(package_file.file)
end end
end end
end end
...@@ -166,7 +190,7 @@ module API ...@@ -166,7 +190,7 @@ module API
else else
track_event('pull_package') if jar_file?(format) track_event('pull_package') if jar_file?(format)
present_carrierwave_file!(package_file.file) present_carrierwave_file_with_head_support!(package_file.file)
end end
end end
......
...@@ -29,6 +29,54 @@ describe API::MavenPackages do ...@@ -29,6 +29,54 @@ describe API::MavenPackages do
end end
end end
shared_examples 'processing HEAD requests' do
subject { head api(url) }
before do
allow_any_instance_of(::Packages::PackageFileUploader).to receive(:fog_credentials).and_return(object_storage_credentials)
stub_package_file_object_storage(enabled: object_storage_enabled)
end
context 'with object storage enabled' do
let(:object_storage_enabled) { true }
before do
allow_any_instance_of(::Packages::PackageFileUploader).to receive(:file_storage?).and_return(false)
end
context 'non AWS provider' do
let(:object_storage_credentials) { { provider: 'Google' } }
it 'does not generated a signed url for head' do
expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
subject
end
end
context 'with AWS provider' do
let(:object_storage_credentials) { { provider: 'AWS', aws_access_key_id: 'test', aws_secret_access_key: 'test' } }
it 'generates a signed url for head' do
expect_any_instance_of(Fog::AWS::Storage::Files).to receive(:head_url).and_call_original
subject
end
end
end
context 'with object storage disabled' do
let(:object_storage_enabled) { false }
let(:object_storage_credentials) { {} }
it 'does not generate a signed url for head' do
expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
subject
end
end
end
describe 'GET /api/v4/packages/maven/*path/:file_name' do describe 'GET /api/v4/packages/maven/*path/:file_name' do
let(:package) { create(:maven_package, project: project, name: project.full_path) } let(:package) { create(:maven_package, project: project, name: project.full_path) }
...@@ -149,6 +197,15 @@ describe API::MavenPackages do ...@@ -149,6 +197,15 @@ describe API::MavenPackages do
end end
end end
describe 'HEAD /api/v4/packages/maven/*path/:file_name' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:package) { create(:maven_package, project: project, name: project.full_path) }
let_it_be(:package_file) { package.package_files.where('file_name like ?', '%.xml').first }
let(:url) { "/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
it_behaves_like 'processing HEAD requests'
end
describe 'GET /api/v4/groups/:id/-/packages/maven/*path/:file_name' do describe 'GET /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
before do before do
project.team.truncate project.team.truncate
...@@ -262,6 +319,16 @@ describe API::MavenPackages do ...@@ -262,6 +319,16 @@ describe API::MavenPackages do
end end
end end
describe 'HEAD /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :public, namespace: group) }
let_it_be(:package) { create(:maven_package, project: project, name: project.full_path) }
let_it_be(:package_file) { package.package_files.where('file_name like ?', '%.xml').first }
let(:url) { "/groups/#{group.id}/-/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
it_behaves_like 'processing HEAD requests'
end
describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do
context 'a public project' do context 'a public project' do
subject { download_file(package_file.file_name) } subject { download_file(package_file.file_name) }
...@@ -340,6 +407,15 @@ describe API::MavenPackages do ...@@ -340,6 +407,15 @@ describe API::MavenPackages do
end end
end end
describe 'HEAD /api/v4/projects/:id/packages/maven/*path/:file_name' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:package) { create(:maven_package, project: project, name: project.full_path) }
let_it_be(:package_file) { package.package_files.where('file_name like ?', '%.xml').first }
let(:url) { "/projects/#{project.id}/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
it_behaves_like 'processing HEAD requests'
end
describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name/authorize' do describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name/authorize' do
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/authorize"), params: {}, headers: headers_with_token put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2F.ssh%2Fauthorized_keys/authorize"), params: {}, headers: headers_with_token
......
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