Raise Mastodon::HostValidationError when host for HTTP request is private (#6410)
This commit is contained in:
		
				
					committed by
					
						 Eugen Rochko
						Eugen Rochko
					
				
			
			
				
	
			
			
			
						parent
						
							7cb49eaa3a
						
					
				
				
					commit
					2e8a492e88
				
			
							
								
								
									
										4
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -96,6 +96,10 @@ group :development, :test do | ||||
|   gem 'rspec-rails', '~> 3.7' | ||||
| end | ||||
|  | ||||
| group :production, :test do | ||||
|   gem 'private_address_check', '~> 0.4.1' | ||||
| end | ||||
|  | ||||
| group :test do | ||||
|   gem 'capybara', '~> 2.15' | ||||
|   gem 'climate_control', '~> 0.2' | ||||
|   | ||||
| @@ -376,6 +376,7 @@ GEM | ||||
|     premailer-rails (1.10.1) | ||||
|       actionmailer (>= 3, < 6) | ||||
|       premailer (~> 1.7, >= 1.7.9) | ||||
|     private_address_check (0.4.1) | ||||
|     pry (0.11.3) | ||||
|       coderay (~> 1.1.0) | ||||
|       method_source (~> 0.9.0) | ||||
| @@ -683,6 +684,7 @@ DEPENDENCIES | ||||
|   pghero (~> 1.7) | ||||
|   pkg-config (~> 1.2) | ||||
|   premailer-rails | ||||
|   private_address_check (~> 0.4.1) | ||||
|   pry-rails (~> 0.3) | ||||
|   puma (~> 3.10) | ||||
|   pundit (~> 1.1) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ module Mastodon | ||||
|   class Error < StandardError; end | ||||
|   class NotPermittedError < Error; end | ||||
|   class ValidationError < Error; end | ||||
|   class HostValidationError < ValidationError; end | ||||
|   class RaceConditionError < Error; end | ||||
|  | ||||
|   class UnexpectedResponseError < Error | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| require 'ipaddr' | ||||
| require 'socket' | ||||
|  | ||||
| class Request | ||||
|   REQUEST_TARGET = '(request-target)' | ||||
|  | ||||
| @@ -8,7 +11,7 @@ class Request | ||||
|   def initialize(verb, url, **options) | ||||
|     @verb    = verb | ||||
|     @url     = Addressable::URI.parse(url).normalize | ||||
|     @options = options | ||||
|     @options = options.merge(socket_class: Socket) | ||||
|     @headers = {} | ||||
|  | ||||
|     set_common_headers! | ||||
| @@ -87,4 +90,18 @@ class Request | ||||
|   def http_client | ||||
|     HTTP.timeout(:per_operation, timeout).follow(max_hops: 2) | ||||
|   end | ||||
|  | ||||
|   class Socket < TCPSocket | ||||
|     class << self | ||||
|       def open(host, *args) | ||||
|         address = IPSocket.getaddress(host) | ||||
|         raise Mastodon::HostValidationError if PrivateAddressCheck.private_address? IPAddr.new(address) | ||||
|         super address, *args | ||||
|       end | ||||
|  | ||||
|       alias new open | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   private_constant :Socket | ||||
| end | ||||
|   | ||||
							
								
								
									
										11
									
								
								app/lib/sidekiq_error_handler.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/lib/sidekiq_error_handler.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| # frozen_string_literal: true | ||||
|  | ||||
| class SidekiqErrorHandler | ||||
|   def call(*) | ||||
|     yield | ||||
|   rescue Mastodon::HostValidationError => e | ||||
|     Rails.logger.error "#{e.class}: #{e.message}" | ||||
|     Rails.logger.error e.backtrace.join("\n") | ||||
|     # Do not retry | ||||
|   end | ||||
| end | ||||
| @@ -85,3 +85,9 @@ Rails.application.configure do | ||||
| end | ||||
|  | ||||
| ActiveRecordQueryTrace.enabled = ENV.fetch('QUERY_TRACE_ENABLED') { false } | ||||
|  | ||||
| module PrivateAddressCheck | ||||
|   def self.private_address?(*) | ||||
|     false | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -9,6 +9,10 @@ end | ||||
|  | ||||
| Sidekiq.configure_server do |config| | ||||
|   config.redis = redis_params | ||||
|  | ||||
|   config.server_middleware do |chain| | ||||
|     chain.add SidekiqErrorHandler | ||||
|   end | ||||
| end | ||||
|  | ||||
| Sidekiq.configure_client do |config| | ||||
|   | ||||
| @@ -38,17 +38,32 @@ describe Request do | ||||
|   end | ||||
|  | ||||
|   describe '#perform' do | ||||
|     before do | ||||
|       stub_request(:get, 'http://example.com') | ||||
|       subject.perform | ||||
|     context 'with valid host' do | ||||
|       before do | ||||
|         stub_request(:get, 'http://example.com') | ||||
|         subject.perform | ||||
|       end | ||||
|  | ||||
|       it 'executes a HTTP request' do | ||||
|         expect(a_request(:get, 'http://example.com')).to have_been_made.once | ||||
|       end | ||||
|  | ||||
|       it 'sets headers' do | ||||
|         expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     it 'executes a HTTP request' do | ||||
|       expect(a_request(:get, 'http://example.com')).to have_been_made.once | ||||
|     end | ||||
|     context 'with private host' do | ||||
|       around do |example| | ||||
|         WebMock.disable! | ||||
|         example.run | ||||
|         WebMock.enable! | ||||
|       end | ||||
|  | ||||
|     it 'sets headers' do | ||||
|       expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made | ||||
|       it 'raises Mastodon::ValidationError' do | ||||
|         allow(IPSocket).to receive(:getaddress).with('example.com').and_return('0.0.0.0') | ||||
|         expect{ subject.perform }.to raise_error Mastodon::ValidationError | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user