Commit 22659d87 authored by Ash McKenzie's avatar Ash McKenzie

Geo clone/pull/push for not yet replicated repos

Also, avoid private project enumeration by
redirecting 'always'.
parent bc6835c2
...@@ -55,7 +55,8 @@ module EE ...@@ -55,7 +55,8 @@ module EE
'lfs_locks_api' => %w{create unlock verify} 'lfs_locks_api' => %w{create unlock verify}
}.freeze }.freeze
def initialize(controller_name, action_name, service) def initialize(project, controller_name, action_name, service)
@project = project
@controller_name = controller_name @controller_name = controller_name
@action_name = action_name @action_name = action_name
@service = service @service = service
...@@ -67,12 +68,20 @@ module EE ...@@ -67,12 +68,20 @@ module EE
def redirect? def redirect?
!!CONTROLLER_AND_ACTIONS_TO_REDIRECT[controller_name]&.include?(action_name) || !!CONTROLLER_AND_ACTIONS_TO_REDIRECT[controller_name]&.include?(action_name) ||
git_receive_pack_request? git_receive_pack_request? ||
redirect_to_avoid_enumeration? ||
not_yet_replicated_redirect?
end
def not_yet_replicated_redirect?
return false unless project
git_upload_pack_request? && !::Geo::ProjectRegistry.repository_replicated_for?(project.id)
end end
private private
attr_reader :service attr_reader :project, :service
# Examples: # Examples:
# #
...@@ -94,6 +103,14 @@ module EE ...@@ -94,6 +103,14 @@ module EE
service_or_action_name == 'git-receive-pack' service_or_action_name == 'git-receive-pack'
end end
# Matches:
#
# GET /repo.git/info/refs?service=git-upload-pack
#
def git_upload_pack_request?
service_or_action_name == 'git-upload-pack'
end
# Matches: # Matches:
# #
# GET /repo.git/info/refs # GET /repo.git/info/refs
...@@ -101,12 +118,23 @@ module EE ...@@ -101,12 +118,23 @@ module EE
def info_refs_request? def info_refs_request?
action_name == 'info_refs' action_name == 'info_refs'
end end
# The purpose of the #redirect_to_avoid_enumeration? method is to avoid
# a scenario where an authenticated user uses the HTTP responses as a
# way of enumerating private projects. Without this check, an attacker
# could determine if a project exists or not by looking at the initial
# HTTP response code for 401 (doesn't exist) vs 302. (exists).
#
def redirect_to_avoid_enumeration?
project.nil?
end
end end
class GeoGitLFSHelper class GeoGitLFSHelper
MINIMUM_GIT_LFS_VERSION = '2.4.2'.freeze MINIMUM_GIT_LFS_VERSION = '2.4.2'.freeze
def initialize(geo_route_helper, operation, current_version) def initialize(project, geo_route_helper, operation, current_version)
@project = project
@geo_route_helper = geo_route_helper @geo_route_helper = geo_route_helper
@operation = operation @operation = operation
@current_version = current_version @current_version = current_version
...@@ -122,6 +150,7 @@ module EE ...@@ -122,6 +150,7 @@ module EE
def redirect? def redirect?
return true if batch_upload? return true if batch_upload?
return true if not_yet_replicated_redirect?
false false
end end
...@@ -134,7 +163,7 @@ module EE ...@@ -134,7 +163,7 @@ module EE
private private
attr_reader :geo_route_helper, :operation, :current_version attr_reader :project, :geo_route_helper, :operation, :current_version
def incorrect_version_message def incorrect_version_message
translation = _("You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com") translation = _("You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com")
...@@ -149,18 +178,32 @@ module EE ...@@ -149,18 +178,32 @@ module EE
batch_request? && operation == 'upload' batch_request? && operation == 'upload'
end end
def batch_download?
batch_request? && operation == 'download'
end
def transfer_download?
geo_route_helper.match?('lfs_storage', 'download')
end
def not_yet_replicated_redirect?
return false unless project
(batch_download? || transfer_download?) && !::Geo::ProjectRegistry.repository_replicated_for?(project.id)
end
def wanted_version def wanted_version
::Gitlab::VersionInfo.parse(MINIMUM_GIT_LFS_VERSION) ::Gitlab::VersionInfo.parse(MINIMUM_GIT_LFS_VERSION)
end end
end end
def geo_route_helper def geo_route_helper
@geo_route_helper ||= GeoRouteHelper.new(controller_name, action_name, params[:service]) @geo_route_helper ||= GeoRouteHelper.new(project, controller_name, action_name, params[:service])
end end
def geo_git_lfs_helper def geo_git_lfs_helper
# params[:operation] explained: https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md#requests # params[:operation] explained: https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md#requests
@geo_git_lfs_helper ||= GeoGitLFSHelper.new(geo_route_helper, params[:operation], request.headers['User-Agent']) @geo_git_lfs_helper ||= GeoGitLFSHelper.new(project, geo_route_helper, params[:operation], request.headers['User-Agent'])
end end
def geo_request_fullpath_for_primary def geo_request_fullpath_for_primary
...@@ -169,7 +212,13 @@ module EE ...@@ -169,7 +212,13 @@ module EE
end end
def geo_primary_full_url def geo_primary_full_url
path = File.join(geo_secondary_referrer_path_prefix, geo_request_fullpath_for_primary) path = if geo_route_helper.not_yet_replicated_redirect?
# git clone/pull
geo_request_fullpath_for_primary
else
# git push
File.join(geo_secondary_referrer_path_prefix, geo_request_fullpath_for_primary)
end
::Gitlab::Utils.append_path(::Gitlab::Geo.primary_node.internal_url, path) ::Gitlab::Utils.append_path(::Gitlab::Geo.primary_node.internal_url, path)
end end
......
---
title: Geo - Support git clone/pull operations for repositories that are not yet replicated
merge_request: 27072
author:
type: added
This diff is collapsed.
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