Raise Mastodon::HostValidationError when host for HTTP request is private (#6410)
This commit is contained in:
parent
7cb49eaa3a
commit
2e8a492e88
8 changed files with 69 additions and 9 deletions
4
Gemfile
4
Gemfile
|
@ -96,6 +96,10 @@ group :development, :test do
|
||||||
gem 'rspec-rails', '~> 3.7'
|
gem 'rspec-rails', '~> 3.7'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
group :production, :test do
|
||||||
|
gem 'private_address_check', '~> 0.4.1'
|
||||||
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 2.15'
|
gem 'capybara', '~> 2.15'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
|
|
|
@ -376,6 +376,7 @@ GEM
|
||||||
premailer-rails (1.10.1)
|
premailer-rails (1.10.1)
|
||||||
actionmailer (>= 3, < 6)
|
actionmailer (>= 3, < 6)
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
|
private_address_check (0.4.1)
|
||||||
pry (0.11.3)
|
pry (0.11.3)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.9.0)
|
method_source (~> 0.9.0)
|
||||||
|
@ -683,6 +684,7 @@ DEPENDENCIES
|
||||||
pghero (~> 1.7)
|
pghero (~> 1.7)
|
||||||
pkg-config (~> 1.2)
|
pkg-config (~> 1.2)
|
||||||
premailer-rails
|
premailer-rails
|
||||||
|
private_address_check (~> 0.4.1)
|
||||||
pry-rails (~> 0.3)
|
pry-rails (~> 0.3)
|
||||||
puma (~> 3.10)
|
puma (~> 3.10)
|
||||||
pundit (~> 1.1)
|
pundit (~> 1.1)
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Mastodon
|
||||||
class Error < StandardError; end
|
class Error < StandardError; end
|
||||||
class NotPermittedError < Error; end
|
class NotPermittedError < Error; end
|
||||||
class ValidationError < Error; end
|
class ValidationError < Error; end
|
||||||
|
class HostValidationError < ValidationError; end
|
||||||
class RaceConditionError < Error; end
|
class RaceConditionError < Error; end
|
||||||
|
|
||||||
class UnexpectedResponseError < Error
|
class UnexpectedResponseError < Error
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'ipaddr'
|
||||||
|
require 'socket'
|
||||||
|
|
||||||
class Request
|
class Request
|
||||||
REQUEST_TARGET = '(request-target)'
|
REQUEST_TARGET = '(request-target)'
|
||||||
|
|
||||||
|
@ -8,7 +11,7 @@ class Request
|
||||||
def initialize(verb, url, **options)
|
def initialize(verb, url, **options)
|
||||||
@verb = verb
|
@verb = verb
|
||||||
@url = Addressable::URI.parse(url).normalize
|
@url = Addressable::URI.parse(url).normalize
|
||||||
@options = options
|
@options = options.merge(socket_class: Socket)
|
||||||
@headers = {}
|
@headers = {}
|
||||||
|
|
||||||
set_common_headers!
|
set_common_headers!
|
||||||
|
@ -87,4 +90,18 @@ class Request
|
||||||
def http_client
|
def http_client
|
||||||
HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
|
HTTP.timeout(:per_operation, timeout).follow(max_hops: 2)
|
||||||
end
|
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
|
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
|
end
|
||||||
|
|
||||||
ActiveRecordQueryTrace.enabled = ENV.fetch('QUERY_TRACE_ENABLED') { false }
|
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|
|
Sidekiq.configure_server do |config|
|
||||||
config.redis = redis_params
|
config.redis = redis_params
|
||||||
|
|
||||||
|
config.server_middleware do |chain|
|
||||||
|
chain.add SidekiqErrorHandler
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Sidekiq.configure_client do |config|
|
Sidekiq.configure_client do |config|
|
||||||
|
|
|
@ -38,6 +38,7 @@ describe Request do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#perform' do
|
describe '#perform' do
|
||||||
|
context 'with valid host' do
|
||||||
before do
|
before do
|
||||||
stub_request(:get, 'http://example.com')
|
stub_request(:get, 'http://example.com')
|
||||||
subject.perform
|
subject.perform
|
||||||
|
@ -51,4 +52,18 @@ describe Request do
|
||||||
expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
|
expect(a_request(:get, 'http://example.com').with(headers: subject.headers)).to have_been_made
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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
|
end
|
||||||
|
|
Loading…
Reference in a new issue