Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/middleware')
-rw-r--r--lib/gitlab/middleware/multipart.rb99
-rw-r--r--lib/gitlab/middleware/read_only/controller.rb2
-rw-r--r--lib/gitlab/middleware/same_site_cookies.rb89
3 files changed, 181 insertions, 9 deletions
diff --git a/lib/gitlab/middleware/multipart.rb b/lib/gitlab/middleware/multipart.rb
index c0b671abd44..8e6ac7610f2 100644
--- a/lib/gitlab/middleware/multipart.rb
+++ b/lib/gitlab/middleware/multipart.rb
@@ -29,6 +29,8 @@ module Gitlab
module Middleware
class Multipart
RACK_ENV_KEY = 'HTTP_GITLAB_WORKHORSE_MULTIPART_FIELDS'
+ JWT_PARAM_SUFFIX = '.gitlab-workhorse-upload'
+ JWT_PARAM_FIXED_KEY = 'upload'
class Handler
def initialize(env, message)
@@ -57,7 +59,8 @@ module Gitlab
yield
ensure
- @open_files.each(&:close)
+ @open_files.compact
+ .each(&:close)
end
# This function calls itself recursively
@@ -122,15 +125,89 @@ module Gitlab
def allowed_paths
[
+ Dir.tmpdir,
::FileUploader.root,
- Gitlab.config.uploads.storage_path,
- JobArtifactUploader.workhorse_upload_path,
- LfsObjectUploader.workhorse_upload_path,
+ ::Gitlab.config.uploads.storage_path,
+ ::JobArtifactUploader.workhorse_upload_path,
+ ::LfsObjectUploader.workhorse_upload_path,
File.join(Rails.root, 'public/uploads/tmp')
] + package_allowed_paths
end
end
+ # TODO this class is meant to replace Handler when the feature flag
+ # upload_middleware_jwt_params_handler is removed
+ class HandlerForJWTParams < Handler
+ def with_open_files
+ @rewritten_fields.keys.each do |field|
+ parsed_field = Rack::Utils.parse_nested_query(field)
+ raise "unexpected field: #{field.inspect}" unless parsed_field.count == 1
+
+ key, value = parsed_field.first
+ if value.nil? # we have a top level param, eg. field = 'foo' and not 'foo[bar]'
+ raise "invalid field: #{field.inspect}" if field != key
+
+ value = open_file(extract_upload_params_from(@request.params, with_prefix: key))
+ @open_files << value
+ else
+ value = decorate_params_value(value, @request.params[key])
+ end
+
+ update_param(key, value)
+ end
+
+ yield
+ ensure
+ @open_files.compact
+ .each(&:close)
+ end
+
+ # This function calls itself recursively
+ def decorate_params_value(hash_path, value_hash)
+ unless hash_path.is_a?(Hash) && hash_path.count == 1
+ raise "invalid path: #{hash_path.inspect}"
+ end
+
+ path_key, path_value = hash_path.first
+
+ unless value_hash.is_a?(Hash) && value_hash[path_key]
+ raise "invalid value hash: #{value_hash.inspect}"
+ end
+
+ case path_value
+ when nil
+ value_hash[path_key] = open_file(extract_upload_params_from(value_hash[path_key]))
+ @open_files << value_hash[path_key]
+ value_hash
+ when Hash
+ decorate_params_value(path_value, value_hash[path_key])
+ value_hash
+ else
+ raise "unexpected path value: #{path_value.inspect}"
+ end
+ end
+
+ def open_file(params)
+ ::UploadedFile.from_params_without_field(params, allowed_paths)
+ end
+
+ private
+
+ def extract_upload_params_from(params, with_prefix: '')
+ param_key = "#{with_prefix}#{JWT_PARAM_SUFFIX}"
+ jwt_token = params[param_key]
+ raise "Empty JWT param: #{param_key}" if jwt_token.blank?
+
+ payload = Gitlab::Workhorse.decode_jwt(jwt_token).first
+ raise "Invalid JWT payload: not a Hash" unless payload.is_a?(Hash)
+
+ upload_params = payload.fetch(JWT_PARAM_FIXED_KEY, {})
+ raise "Empty params for: #{param_key}" if upload_params.empty?
+
+ upload_params
+ end
+ end
+
def initialize(app)
@app = app
end
@@ -139,14 +216,24 @@ module Gitlab
encoded_message = env.delete(RACK_ENV_KEY)
return @app.call(env) if encoded_message.blank?
- message = Gitlab::Workhorse.decode_jwt(encoded_message)[0]
+ message = ::Gitlab::Workhorse.decode_jwt(encoded_message)[0]
- Handler.new(env, message).with_open_files do
+ handler_class.new(env, message).with_open_files do
@app.call(env)
end
rescue UploadedFile::InvalidPathError => e
[400, { 'Content-Type' => 'text/plain' }, e.message]
end
+
+ private
+
+ def handler_class
+ if Feature.enabled?(:upload_middleware_jwt_params_handler)
+ ::Gitlab::Middleware::Multipart::HandlerForJWTParams
+ else
+ ::Gitlab::Middleware::Multipart::Handler
+ end
+ end
end
end
end
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
index 6573506e926..cfea4aaddf3 100644
--- a/lib/gitlab/middleware/read_only/controller.rb
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -36,7 +36,7 @@ module Gitlab
def call
if disallowed_request? && Gitlab::Database.read_only?
- Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.debug('GitLab ReadOnly: preventing possible non read-only operation')
if json_request?
return [403, { 'Content-Type' => APPLICATION_JSON }, [{ 'message' => ERROR_MESSAGE }.to_json]]
diff --git a/lib/gitlab/middleware/same_site_cookies.rb b/lib/gitlab/middleware/same_site_cookies.rb
index 45968035e79..37ccc5abb10 100644
--- a/lib/gitlab/middleware/same_site_cookies.rb
+++ b/lib/gitlab/middleware/same_site_cookies.rb
@@ -30,6 +30,7 @@ module Gitlab
set_cookie = headers['Set-Cookie']&.strip
return result if set_cookie.blank? || !ssl?
+ return result if same_site_none_incompatible?(env['HTTP_USER_AGENT'])
cookies = set_cookie.split(COOKIE_SEPARATOR)
@@ -39,11 +40,11 @@ module Gitlab
# Chrome will drop SameSite=None cookies without the Secure
# flag. If we remove this middleware, we may need to ensure
# that all cookies set this flag.
- if ssl? && !(cookie =~ /;\s*secure/i)
+ unless SECURE_REGEX.match?(cookie)
cookie << '; Secure'
end
- unless cookie =~ /;\s*samesite=/i
+ unless SAME_SITE_REGEX.match?(cookie)
cookie << '; SameSite=None'
end
end
@@ -55,9 +56,93 @@ module Gitlab
private
+ # Taken from https://www.chromium.org/updates/same-site/incompatible-clients
+ # We use RE2 instead of the browser gem for performance.
+ IOS_REGEX = RE2('\(iP.+; CPU .*OS (\d+)[_\d]*.*\) AppleWebKit\/')
+ MACOS_REGEX = RE2('\(Macintosh;.*Mac OS X (\d+)_(\d+)[_\d]*.*\) AppleWebKit\/')
+ SAFARI_REGEX = RE2('Version\/.* Safari\/')
+ CHROMIUM_REGEX = RE2('Chrom(e|ium)')
+ CHROMIUM_VERSION_REGEX = RE2('Chrom[^ \/]+\/(\d+)')
+ UC_BROWSER_REGEX = RE2('UCBrowser\/')
+ UC_BROWSER_VERSION_REGEX = RE2('UCBrowser\/(\d+)\.(\d+)\.(\d+)')
+
+ SECURE_REGEX = RE2(';\s*secure', case_sensitive: false)
+ SAME_SITE_REGEX = RE2(';\s*samesite=', case_sensitive: false)
+
def ssl?
Gitlab.config.gitlab.https
end
+
+ def same_site_none_incompatible?(user_agent)
+ return false if user_agent.blank?
+
+ has_webkit_same_site_bug?(user_agent) || drops_unrecognized_same_site_cookies?(user_agent)
+ end
+
+ def has_webkit_same_site_bug?(user_agent)
+ ios_version?(12, user_agent) ||
+ (macos_version?(10, 14, user_agent) && safari?(user_agent))
+ end
+
+ def drops_unrecognized_same_site_cookies?(user_agent)
+ if uc_browser?(user_agent)
+ return !uc_browser_version_at_least?(12, 13, 2, user_agent)
+ end
+
+ chromium_based?(user_agent) && chromium_version_between?(51, 66, user_agent)
+ end
+
+ def ios_version?(major, user_agent)
+ m = IOS_REGEX.match(user_agent)
+
+ return false if m.nil?
+
+ m[1].to_i == major
+ end
+
+ def macos_version?(major, minor, user_agent)
+ m = MACOS_REGEX.match(user_agent)
+
+ return false if m.nil?
+
+ m[1].to_i == major && m[2].to_i == minor
+ end
+
+ def safari?(user_agent)
+ SAFARI_REGEX.match?(user_agent)
+ end
+
+ def chromium_based?(user_agent)
+ CHROMIUM_REGEX.match?(user_agent)
+ end
+
+ def chromium_version_between?(from_major, to_major, user_agent)
+ m = CHROMIUM_VERSION_REGEX.match(user_agent)
+
+ return false if m.nil?
+
+ version = m[1].to_i
+ version >= from_major && version <= to_major
+ end
+
+ def uc_browser?(user_agent)
+ UC_BROWSER_REGEX.match?(user_agent)
+ end
+
+ def uc_browser_version_at_least?(major, minor, build, user_agent)
+ m = UC_BROWSER_VERSION_REGEX.match(user_agent)
+
+ return false if m.nil?
+
+ major_version = m[1].to_i
+ minor_version = m[2].to_i
+ build_version = m[3].to_i
+
+ return major_version > major if major_version != major
+ return minor_version > minor if minor_version != minor
+
+ build_version >= build
+ end
end
end
end