Whitelist LFS batch URL on Geo Geo read-only middleware

parent 4a78663e
...@@ -2,6 +2,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -2,6 +2,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
include LfsRequest include LfsRequest
skip_before_action :lfs_check_access!, only: [:deprecated] skip_before_action :lfs_check_access!, only: [:deprecated]
before_action :lfs_check_batch_operation!, only: [:batch]
def batch def batch
unless objects.present? unless objects.present?
...@@ -90,4 +91,16 @@ class Projects::LfsApiController < Projects::GitHttpClientController ...@@ -90,4 +91,16 @@ class Projects::LfsApiController < Projects::GitHttpClientController
} }
} }
end end
def lfs_check_batch_operation!
if upload_request? && Gitlab::Geo.secondary?
render(
json: {
message: 'You cannot do writing operations on a secondary GitLab Geo instance'
},
content_type: "application/vnd.git-lfs+json",
status: 403
)
end
end
end end
module Gitlab module Gitlab
module Middleware module Middleware
class ReadonlyGeo class ReadonlyGeo
DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze
APPLICATION_JSON = 'application/json'.freeze APPLICATION_JSON = 'application/json'.freeze
API_VERSIONS = (3..4) API_VERSIONS = (3..4)
DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze
DOWNLOAD_OPERATION = 'download'.freeze
def initialize(app) def initialize(app)
@app = app @app = app
...@@ -66,7 +65,7 @@ module Gitlab ...@@ -66,7 +65,7 @@ module Gitlab
end end
def whitelisted_routes def whitelisted_routes
logout_route || grack_route || @whitelisted.any? { |path| request.path.include?(path) } || lfs_download_route logout_route || grack_route || @whitelisted.any? { |path| request.path.include?(path) } || lfs_route
end end
def logout_route def logout_route
...@@ -81,21 +80,8 @@ module Gitlab ...@@ -81,21 +80,8 @@ module Gitlab
request.path.end_with?('.git/git-upload-pack') request.path.end_with?('.git/git-upload-pack')
end end
def lfs_download_route def lfs_route
request.path.end_with?('/info/lfs/objects/batch') && lfs_download_operation? request.path.end_with?('/info/lfs/objects/batch')
end
def lfs_download_operation?
params = parse_formatted_parameters
params[:operation] == DOWNLOAD_OPERATION
end
def parse_formatted_parameters
return {} if request.content_length.to_i.zero?
data = ActiveSupport::JSON.decode(request.body.read) rescue {}
request.body.rewind
data.with_indifferent_access
end end
end end
end end
......
...@@ -35,6 +35,7 @@ describe Gitlab::Middleware::ReadonlyGeo do ...@@ -35,6 +35,7 @@ describe Gitlab::Middleware::ReadonlyGeo do
end end
subject { described_class.new(fake_app) } subject { described_class.new(fake_app) }
let(:request) { Rack::MockRequest.new(rack_stack) } let(:request) { Rack::MockRequest.new(rack_stack) }
context 'normal requests to a secondary Gitlab Geo' do context 'normal requests to a secondary Gitlab Geo' do
...@@ -48,21 +49,21 @@ describe Gitlab::Middleware::ReadonlyGeo do ...@@ -48,21 +49,21 @@ describe Gitlab::Middleware::ReadonlyGeo do
response = request.patch('/test_request') response = request.patch('/test_request')
expect(response).to be_a_redirect expect(response).to be_a_redirect
expect(subject).to disallow_request is_expected.to disallow_request
end end
it 'expects PUT requests to be disallowed' do it 'expects PUT requests to be disallowed' do
response = request.put('/test_request') response = request.put('/test_request')
expect(response).to be_a_redirect expect(response).to be_a_redirect
expect(subject).to disallow_request is_expected.to disallow_request
end end
it 'expects POST requests to be disallowed' do it 'expects POST requests to be disallowed' do
response = request.post('/test_request') response = request.post('/test_request')
expect(response).to be_a_redirect expect(response).to be_a_redirect
expect(subject).to disallow_request is_expected.to disallow_request
end end
it 'expects a internal POST request to be allowed after a disallowed request' do it 'expects a internal POST request to be allowed after a disallowed request' do
...@@ -79,22 +80,7 @@ describe Gitlab::Middleware::ReadonlyGeo do ...@@ -79,22 +80,7 @@ describe Gitlab::Middleware::ReadonlyGeo do
response = request.delete('/test_request') response = request.delete('/test_request')
expect(response).to be_a_redirect expect(response).to be_a_redirect
expect(subject).to disallow_request is_expected.to disallow_request
end
it 'expects a POST LFS request to upload objects to be disallowed' do
body = ActiveSupport::JSON.encode({
'operation' => 'upload',
'objects' => [
{ 'oid' => '12345678',
'size' => '123' }
]
})
response = request.post('/root/rouge.git/info/lfs/objects/batch', input: StringIO.new(body))
expect(response).to be_a_redirect
expect(subject).to disallow_request
end end
context 'whitelisted requests' do context 'whitelisted requests' do
...@@ -102,36 +88,28 @@ describe Gitlab::Middleware::ReadonlyGeo do ...@@ -102,36 +88,28 @@ describe Gitlab::Middleware::ReadonlyGeo do
response = request.delete('/users/sign_out') response = request.delete('/users/sign_out')
expect(response).not_to be_a_redirect expect(response).not_to be_a_redirect
expect(subject).not_to disallow_request is_expected.not_to disallow_request
end end
it 'expects a POST internal request to be allowed' do it 'expects a POST internal request to be allowed' do
response = request.post("/api/#{API::API.version}/internal") response = request.post("/api/#{API::API.version}/internal")
expect(response).not_to be_a_redirect expect(response).not_to be_a_redirect
expect(subject).not_to disallow_request is_expected.not_to disallow_request
end end
it 'expects a GET status request to be allowed' do it 'expects a GET status request to be allowed' do
response = request.get("/api/#{API::API.version}/geo/status") response = request.get("/api/#{API::API.version}/geo/status")
expect(response).not_to be_a_redirect expect(response).not_to be_a_redirect
expect(subject).not_to disallow_request is_expected.not_to disallow_request
end end
it 'expects a POST LFS request to download objects to be allowed' do it 'expects a POST LFS request to batch URL to be allowed' do
body = ActiveSupport::JSON.encode({ response = request.post('/root/rouge.git/info/lfs/objects/batch')
'operation' => 'download',
'objects' => [
{ 'oid' => '12345678',
'size' => '123' }
]
})
response = request.post('/root/rouge.git/info/lfs/objects/batch', input: StringIO.new(body))
expect(response).not_to be_a_redirect expect(response).not_to be_a_redirect
expect(subject).not_to disallow_request is_expected.not_to disallow_request
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