diff options
Diffstat (limited to 'lib/gitlab/url_blocker.rb')
-rw-r--r-- | lib/gitlab/url_blocker.rb | 81 |
1 files changed, 61 insertions, 20 deletions
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index 00e609511f2..1be9190e5f8 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -7,6 +7,8 @@ module Gitlab class UrlBlocker BlockedUrlError = Class.new(StandardError) + DENY_ALL_REQUESTS_EXCEPT_ALLOWED_DEFAULT = proc { deny_all_requests_except_allowed_app_setting }.freeze + class << self # Validates the given url according to the constraints specified by arguments. # @@ -17,6 +19,7 @@ module Gitlab # ascii_only - Raises error if URL has unicode characters and argument is true. # enforce_user - Raises error if URL user doesn't start with alphanumeric characters and argument is true. # enforce_sanitization - Raises error if URL includes any HTML/CSS/JS tags and argument is true. + # deny_all_requests_except_allowed - Raises error if URL is not in the allow list and argument is true. Can be Boolean or Proc. Defaults to instance app setting. # # Returns an array with [<uri>, <original-hostname>]. # rubocop:disable Metrics/ParameterLists @@ -30,6 +33,7 @@ module Gitlab ascii_only: false, enforce_user: false, enforce_sanitization: false, + deny_all_requests_except_allowed: DENY_ALL_REQUESTS_EXCEPT_ALLOWED_DEFAULT, dns_rebind_protection: true) # rubocop:enable Metrics/ParameterLists @@ -49,21 +53,28 @@ module Gitlab ascii_only: ascii_only ) - address_info = get_address_info(uri, dns_rebind_protection) - return [uri, nil] unless address_info + begin + address_info = get_address_info(uri) + rescue SocketError + return [uri, nil] unless enforce_address_info_retrievable?(uri, dns_rebind_protection, deny_all_requests_except_allowed) + + raise BlockedUrlError, 'Host cannot be resolved or invalid' + end ip_address = ip_address(address_info) - return [uri, nil] if domain_allowed?(uri) + return [uri, nil] if domain_in_allow_list?(uri) protected_uri_with_hostname = enforce_uri_hostname(ip_address, uri, dns_rebind_protection) - return protected_uri_with_hostname if ip_allowed?(ip_address, port: get_port(uri)) + return protected_uri_with_hostname if ip_in_allow_list?(ip_address, port: get_port(uri)) # Allow url from the GitLab instance itself but only for the configured hostname and ports return protected_uri_with_hostname if internal?(uri) return protected_uri_with_hostname if allow_object_storage && object_storage_endpoint?(uri) + validate_deny_all_requests_except_allowed!(deny_all_requests_except_allowed) + validate_local_request( address_info: address_info, allow_localhost: allow_localhost, @@ -115,29 +126,41 @@ module Gitlab validate_unicode_restriction(uri) if ascii_only end - def get_address_info(uri, dns_rebind_protection) + # Returns addrinfo object for the URI. + # + # @param uri [Addressable::URI] + # + # @raise [Gitlab::UrlBlocker::BlockedUrlError, ArgumentError] - BlockedUrlError raised if host is too long. + # + # @return [Array<Addrinfo>] + def get_address_info(uri) Addrinfo.getaddrinfo(uri.hostname, get_port(uri), nil, :STREAM).map do |addr| addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr end - rescue SocketError - # If the dns rebinding protection is not enabled or the domain - # is allowed we avoid the dns rebinding checks - return if domain_allowed?(uri) || !dns_rebind_protection + rescue ArgumentError => error + # Addrinfo.getaddrinfo errors if the domain exceeds 1024 characters. + raise unless error.message.include?('hostname too long') + + raise BlockedUrlError, "Host is too long (maximum is 1024 characters)" + end + + def enforce_address_info_retrievable?(uri, dns_rebind_protection, deny_all_requests_except_allowed) + # Do not enforce if URI is in the allow list + return false if domain_in_allow_list?(uri) + + # Enforce if the instance should block requests + return true if deny_all_requests_except_allowed?(deny_all_requests_except_allowed) + + # Do not enforce unless DNS rebinding protection is enabled + return false unless dns_rebind_protection # In the test suite we use a lot of mocked urls that are either invalid or # don't exist. In order to avoid modifying a ton of tests and factories # we allow invalid urls unless the environment variable RSPEC_ALLOW_INVALID_URLS # is not true - return if Rails.env.test? && ENV['RSPEC_ALLOW_INVALID_URLS'] == 'true' - - # If the addr can't be resolved or the url is invalid (i.e http://1.1.1.1.1) - # we block the url - raise BlockedUrlError, "Host cannot be resolved or invalid" - rescue ArgumentError => error - # Addrinfo.getaddrinfo errors if the domain exceeds 1024 characters. - raise unless error.message.include?('hostname too long') + return false if Rails.env.test? && ENV['RSPEC_ALLOW_INVALID_URLS'] == 'true' - raise BlockedUrlError, "Host is too long (maximum is 1024 characters)" + true end def validate_local_request( @@ -260,6 +283,15 @@ module Gitlab raise BlockedUrlError, "Requests to the link local network are not allowed" end + # Raises a BlockedUrlError if the instance is configured to deny all requests. + # + # This should only be called after allow list checks have been made. + def validate_deny_all_requests_except_allowed!(should_deny) + return unless deny_all_requests_except_allowed?(should_deny) + + raise BlockedUrlError, "Requests to hosts and IP addresses not on the Allow List are denied" + end + # Raises a BlockedUrlError if any IP in `addrs_info` is the limited # broadcast address. # https://datatracker.ietf.org/doc/html/rfc919#section-7 @@ -302,6 +334,15 @@ module Gitlab end.compact.uniq end + def deny_all_requests_except_allowed?(should_deny) + should_deny.is_a?(Proc) ? should_deny.call : should_deny + end + + def deny_all_requests_except_allowed_app_setting + Gitlab::CurrentSettings.current_application_settings? && + Gitlab::CurrentSettings.deny_all_requests_except_allowed? + end + def object_storage_endpoint?(uri) enabled_object_storage_endpoints.any? do |endpoint| endpoint_uri = URI(endpoint) @@ -312,11 +353,11 @@ module Gitlab end end - def domain_allowed?(uri) + def domain_in_allow_list?(uri) Gitlab::UrlBlockers::UrlAllowlist.domain_allowed?(uri.normalized_host, port: get_port(uri)) end - def ip_allowed?(ip_address, port: nil) + def ip_in_allow_list?(ip_address, port: nil) Gitlab::UrlBlockers::UrlAllowlist.ip_allowed?(ip_address, port: port) end |