Commit ca3ac309 authored by Aakriti Gupta's avatar Aakriti Gupta

Block LFS writes in read-only mode

parent 39070f5d
---
title: Block LFS writes when database is read-only but allow on Geo secondaries
merge_request: 47684
author:
type: changed
...@@ -20,11 +20,15 @@ module EE ...@@ -20,11 +20,15 @@ module EE
'repositories/git_http' => %w{git_receive_pack} 'repositories/git_http' => %w{git_receive_pack}
}.freeze }.freeze
ALLOWLISTED_GIT_LFS_LOCKS_ROUTES = {
'repositories/lfs_locks_api' => %w{verify create unlock}
}.freeze
private private
override :allowlisted_routes override :allowlisted_routes
def allowlisted_routes def allowlisted_routes
super || geo_node_update_route? || geo_proxy_git_ssh_route? || geo_api_route? || geo_proxy_git_http_route? super || geo_node_update_route? || geo_proxy_git_ssh_route? || geo_api_route? || geo_proxy_git_http_route? || lfs_locks_route?
end end
def geo_node_update_route? def geo_node_update_route?
...@@ -58,6 +62,18 @@ module EE ...@@ -58,6 +62,18 @@ module EE
request.path.include?("/api/v#{version}/geo_replication") request.path.include?("/api/v#{version}/geo_replication")
end end
end end
def lfs_locks_route?
# Calling route_hash may be expensive. Only do it if we think there's a possible match
return unless ::Gitlab::Geo.secondary?
unless request.path.end_with?('/info/lfs/locks', '/info/lfs/locks/verify') ||
%r{/info/lfs/locks/\d+/unlock\z}.match?(request.path)
return false
end
ALLOWLISTED_GIT_LFS_LOCKS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
end end
end end
end end
......
...@@ -35,13 +35,50 @@ RSpec.shared_examples 'write access for a read-only GitLab (EE) instance' do ...@@ -35,13 +35,50 @@ RSpec.shared_examples 'write access for a read-only GitLab (EE) instance' do
it_behaves_like 'allowlisted request', :post, '/admin/geo/replication/projects/1/force_redownload' it_behaves_like 'allowlisted request', :post, '/admin/geo/replication/projects/1/force_redownload'
it_behaves_like 'allowlisted request', :delete, '/admin/geo/replication/uploads/1' it_behaves_like 'allowlisted request', :delete, '/admin/geo/replication/uploads/1'
end
it 'expects geo replication node api requests to be allowed' do context 'on Geo secondary' do
response = request.post("/api/#{API::API.version}/geo_replication/designs/resync") before do
allow(::Gitlab::Geo).to receive(:secondary?).and_return(true)
end
where(:description, :path) do
'LFS request to batch' | '/root/rouge.git/info/lfs/objects/batch'
'LFS request to locks verify' | '/root/rouge.git/info/lfs/locks/verify'
'LFS request to locks create' | '/root/rouge.git/info/lfs/locks'
'LFS request to locks unlock' | '/root/rouge.git/info/lfs/locks/1/unlock'
'to geo replication node api' | "/api/#{API::API.version}/geo_replication/designs/resync"
end
with_them do
it "expects a POST #{description} URL to be allowed" do
response = request.post(path)
expect(response).not_to be_redirect
expect(subject).not_to disallow_request
end
end
end
expect(response).not_to be_redirect context 'when not on Geo secondary' do
expect(subject).not_to disallow_request before do
allow(::Gitlab::Geo).to receive(:secondary?).and_return(false)
end
where(:description, :path) do
'LFS request to locks verify' | '/root/rouge.git/info/lfs/locks/verify'
'LFS request to locks create' | '/root/rouge.git/info/lfs/locks'
'LFS request to locks unlock' | '/root/rouge.git/info/lfs/locks/1/unlock'
end
with_them do
it "expects a POST #{description} URL not to be allowed" do
response = request.post(path)
expect(response).to be_redirect
expect(subject).to disallow_request
end
end
end
end end
it 'expects a POST request to git-receive-pack URL to be allowed' do it 'expects a POST request to git-receive-pack URL to be allowed' do
......
...@@ -13,9 +13,8 @@ module Gitlab ...@@ -13,9 +13,8 @@ module Gitlab
'repositories/git_http' => %w{git_upload_pack} 'repositories/git_http' => %w{git_upload_pack}
}.freeze }.freeze
ALLOWLISTED_GIT_LFS_ROUTES = { ALLOWLISTED_GIT_LFS_BATCH_ROUTES = {
'repositories/lfs_api' => %w{batch}, 'repositories/lfs_api' => %w{batch}
'repositories/lfs_locks_api' => %w{verify create unlock}
}.freeze }.freeze
ALLOWLISTED_GIT_REVISION_ROUTES = { ALLOWLISTED_GIT_REVISION_ROUTES = {
...@@ -88,7 +87,7 @@ module Gitlab ...@@ -88,7 +87,7 @@ module Gitlab
# Overridden in EE module # Overridden in EE module
def allowlisted_routes def allowlisted_routes
workhorse_passthrough_route? || internal_route? || lfs_route? || compare_git_revisions_route? || sidekiq_route? || session_route? || graphql_query? workhorse_passthrough_route? || internal_route? || lfs_batch_route? || compare_git_revisions_route? || sidekiq_route? || session_route? || graphql_query?
end end
# URL for requests passed through gitlab-workhorse to rails-web # URL for requests passed through gitlab-workhorse to rails-web
...@@ -112,15 +111,13 @@ module Gitlab ...@@ -112,15 +111,13 @@ module Gitlab
ALLOWLISTED_GIT_REVISION_ROUTES[route_hash[:controller]]&.include?(route_hash[:action]) ALLOWLISTED_GIT_REVISION_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end end
def lfs_route? # Batch upload requests are blocked in:
# https://gitlab.com/gitlab-org/gitlab/blob/master/app/controllers/repositories/lfs_api_controller.rb#L106
def lfs_batch_route?
# Calling route_hash may be expensive. Only do it if we think there's a possible match # Calling route_hash may be expensive. Only do it if we think there's a possible match
unless request.path.end_with?('/info/lfs/objects/batch', return unless request.path.end_with?('/info/lfs/objects/batch')
'/info/lfs/locks', '/info/lfs/locks/verify') ||
%r{/info/lfs/locks/\d+/unlock\z}.match?(request.path)
return false
end
ALLOWLISTED_GIT_LFS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action]) ALLOWLISTED_GIT_LFS_BATCH_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end end
def session_route? def session_route?
......
...@@ -124,9 +124,6 @@ RSpec.shared_examples 'write access for a read-only GitLab instance' do ...@@ -124,9 +124,6 @@ RSpec.shared_examples 'write access for a read-only GitLab instance' do
where(:description, :path) do where(:description, :path) do
'LFS request to batch' | '/root/rouge.git/info/lfs/objects/batch' 'LFS request to batch' | '/root/rouge.git/info/lfs/objects/batch'
'LFS request to locks verify' | '/root/rouge.git/info/lfs/locks/verify'
'LFS request to locks create' | '/root/rouge.git/info/lfs/locks'
'LFS request to locks unlock' | '/root/rouge.git/info/lfs/locks/1/unlock'
'request to git-upload-pack' | '/root/rouge.git/git-upload-pack' 'request to git-upload-pack' | '/root/rouge.git/git-upload-pack'
end end
...@@ -139,6 +136,21 @@ RSpec.shared_examples 'write access for a read-only GitLab instance' do ...@@ -139,6 +136,21 @@ RSpec.shared_examples 'write access for a read-only GitLab instance' do
expect(subject).not_to disallow_request expect(subject).not_to disallow_request
end end
end end
where(:description, :path) do
'LFS request to locks verify' | '/root/rouge.git/info/lfs/locks/verify'
'LFS request to locks create' | '/root/rouge.git/info/lfs/locks'
'LFS request to locks unlock' | '/root/rouge.git/info/lfs/locks/1/unlock'
end
with_them do
it "expects a POST #{description} URL not to be allowed" do
response = request.post(path)
expect(response).to be_redirect
expect(subject).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