logo

mastofe

My custom branche(s) on git.pleroma.social/pleroma/mastofe
commit: 2e8a492e8843aa958c53636b24cf4d344e7ca47d
parent: 7cb49eaa3aad03b60a1e1620d2f700d6ed2b3ea0
Author: Akihiko Odaki <akihiko.odaki.4i@stu.hosei.ac.jp>
Date:   Sun, 25 Feb 2018 03:16:11 +0900

Raise Mastodon::HostValidationError when host for HTTP request is private (#6410)


Diffstat:

MGemfile4++++
MGemfile.lock2++
Mapp/lib/exceptions.rb1+
Mapp/lib/request.rb19++++++++++++++++++-
Aapp/lib/sidekiq_error_handler.rb11+++++++++++
Mconfig/environments/development.rb6++++++
Mconfig/initializers/sidekiq.rb4++++
Mspec/lib/request_spec.rb31+++++++++++++++++++++++--------
8 files changed, 69 insertions(+), 9 deletions(-)

diff --git a/Gemfile b/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' diff --git a/Gemfile.lock b/Gemfile.lock @@ -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) diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb @@ -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 diff --git a/app/lib/request.rb b/app/lib/request.rb @@ -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 diff --git a/app/lib/sidekiq_error_handler.rb b/app/lib/sidekiq_error_handler.rb @@ -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 diff --git a/config/environments/development.rb b/config/environments/development.rb @@ -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 diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb @@ -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| diff --git a/spec/lib/request_spec.rb b/spec/lib/request_spec.rb @@ -38,17 +38,32 @@ describe Request do end describe '#perform' do - before do - stub_request(:get, 'http://example.com') - subject.perform - end + 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 'executes a HTTP request' do - expect(a_request(:get, 'http://example.com')).to have_been_made.once + it 'sets headers' do + expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made + end end - it 'sets headers' do - expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made + context 'with private host' do + around do |example| + WebMock.disable! + example.run + WebMock.enable! + end + + 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