Commit b067dc7f authored by Arturo Herrero's avatar Arturo Herrero Committed by Bob Van Landuyt

HTTP connection adapter with proxy settings

This updates the HTTPConnectionAdapter to prevent any SSRF protection
bypass when using the proxy settings.
parent de3c64af
...@@ -11,13 +11,18 @@ ...@@ -11,13 +11,18 @@
# This option will take precedence over the global setting. # This option will take precedence over the global setting.
module Gitlab module Gitlab
class HTTPConnectionAdapter < HTTParty::ConnectionAdapter class HTTPConnectionAdapter < HTTParty::ConnectionAdapter
extend ::Gitlab::Utils::Override
override :connection
def connection def connection
begin @uri, hostname = validate_url!(uri)
@uri, hostname = Gitlab::UrlBlocker.validate!(uri, allow_local_network: allow_local_requests?,
allow_localhost: allow_local_requests?, if options.key?(:http_proxyaddr)
dns_rebind_protection: dns_rebind_protection?) proxy_uri_with_port = uri_with_port(options[:http_proxyaddr], options[:http_proxyport])
rescue Gitlab::UrlBlocker::BlockedUrlError => e proxy_uri_validated = validate_url!(proxy_uri_with_port).first
raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
@options[:http_proxyaddr] = proxy_uri_validated.omit(:port).to_s
@options[:http_proxyport] = proxy_uri_validated.port
end end
super.tap do |http| super.tap do |http|
...@@ -27,6 +32,14 @@ module Gitlab ...@@ -27,6 +32,14 @@ module Gitlab
private private
def validate_url!(url)
Gitlab::UrlBlocker.validate!(url, allow_local_network: allow_local_requests?,
allow_localhost: allow_local_requests?,
dns_rebind_protection: dns_rebind_protection?)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise Gitlab::HTTP::BlockedUrlError, "URL '#{url}' is blocked: #{e.message}"
end
def allow_local_requests? def allow_local_requests?
options.fetch(:allow_local_requests, allow_settings_local_requests?) options.fetch(:allow_local_requests, allow_settings_local_requests?)
end end
...@@ -40,5 +53,11 @@ module Gitlab ...@@ -40,5 +53,11 @@ module Gitlab
def allow_settings_local_requests? def allow_settings_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services? Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end end
def uri_with_port(address, port)
uri = Addressable::URI.parse(address)
uri.port = port if port.present?
uri
end
end end
end end
...@@ -5,17 +5,32 @@ require 'spec_helper' ...@@ -5,17 +5,32 @@ require 'spec_helper'
RSpec.describe Gitlab::HTTPConnectionAdapter do RSpec.describe Gitlab::HTTPConnectionAdapter do
include StubRequests include StubRequests
let(:uri) { URI('https://example.org') }
let(:options) { {} }
subject(:connection) { described_class.new(uri, options).connection }
describe '#connection' do describe '#connection' do
before do before do
stub_all_dns('https://example.org', ip_address: '93.184.216.34') stub_all_dns('https://example.org', ip_address: '93.184.216.34')
end end
context 'when local requests are not allowed' do context 'when local requests are allowed' do
let(:options) { { allow_local_requests: true } }
it 'sets up the connection' do it 'sets up the connection' do
uri = URI('https://example.org') expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('93.184.216.34')
expect(connection.hostname_override).to eq('example.org')
expect(connection.addr_port).to eq('example.org')
expect(connection.port).to eq(443)
end
end
connection = described_class.new(uri).connection context 'when local requests are not allowed' do
let(:options) { { allow_local_requests: false } }
it 'sets up the connection' do
expect(connection).to be_a(Net::HTTP) expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('93.184.216.34') expect(connection.address).to eq('93.184.216.34')
expect(connection.hostname_override).to eq('example.org') expect(connection.hostname_override).to eq('example.org')
...@@ -23,28 +38,57 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do ...@@ -23,28 +38,57 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
expect(connection.port).to eq(443) expect(connection.port).to eq(443)
end end
it 'raises error when it is a request to local address' do context 'when it is a request to local network' do
uri = URI('http://172.16.0.0/12') let(:uri) { URI('http://172.16.0.0/12') }
expect { described_class.new(uri).connection } it 'raises error' do
.to raise_error(Gitlab::HTTP::BlockedUrlError, expect { subject }.to raise_error(
"URL 'http://172.16.0.0/12' is blocked: Requests to the local network are not allowed") Gitlab::HTTP::BlockedUrlError,
"URL 'http://172.16.0.0/12' is blocked: Requests to the local network are not allowed"
)
end end
it 'raises error when it is a request to localhost address' do context 'when local request allowed' do
uri = URI('http://127.0.0.1') let(:options) { { allow_local_requests: true } }
expect { described_class.new(uri).connection } it 'sets up the connection' do
.to raise_error(Gitlab::HTTP::BlockedUrlError, expect(connection).to be_a(Net::HTTP)
"URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed") expect(connection.address).to eq('172.16.0.0')
expect(connection.hostname_override).to be(nil)
expect(connection.addr_port).to eq('172.16.0.0')
expect(connection.port).to eq(80)
end
end
end end
context 'when port different from URL scheme is used' do context 'when it is a request to local address' do
it 'sets up the addr_port accordingly' do let(:uri) { URI('http://127.0.0.1') }
uri = URI('https://example.org:8080')
it 'raises error' do
expect { subject }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
"URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed"
)
end
context 'when local request allowed' do
let(:options) { { allow_local_requests: true } }
it 'sets up the connection' do
expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('127.0.0.1')
expect(connection.hostname_override).to be(nil)
expect(connection.addr_port).to eq('127.0.0.1')
expect(connection.port).to eq(80)
end
end
end
connection = described_class.new(uri).connection context 'when port different from URL scheme is used' do
let(:uri) { URI('https://example.org:8080') }
it 'sets up the addr_port accordingly' do
expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('93.184.216.34') expect(connection.address).to eq('93.184.216.34')
expect(connection.hostname_override).to eq('example.org') expect(connection.hostname_override).to eq('example.org')
expect(connection.addr_port).to eq('example.org:8080') expect(connection.addr_port).to eq('example.org:8080')
...@@ -54,13 +98,11 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do ...@@ -54,13 +98,11 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
end end
context 'when DNS rebinding protection is disabled' do context 'when DNS rebinding protection is disabled' do
it 'sets up the connection' do before do
stub_application_setting(dns_rebinding_protection_enabled: false) stub_application_setting(dns_rebinding_protection_enabled: false)
end
uri = URI('https://example.org') it 'sets up the connection' do
connection = described_class.new(uri).connection
expect(connection).to be_a(Net::HTTP) expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('example.org') expect(connection.address).to eq('example.org')
expect(connection.hostname_override).to eq(nil) expect(connection.hostname_override).to eq(nil)
...@@ -70,13 +112,11 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do ...@@ -70,13 +112,11 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
end end
context 'when http(s) environment variable is set' do context 'when http(s) environment variable is set' do
it 'sets up the connection' do before do
stub_env('https_proxy' => 'https://my.proxy') stub_env('https_proxy' => 'https://my.proxy')
end
uri = URI('https://example.org') it 'sets up the connection' do
connection = described_class.new(uri).connection
expect(connection).to be_a(Net::HTTP) expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('example.org') expect(connection.address).to eq('example.org')
expect(connection.hostname_override).to eq(nil) expect(connection.hostname_override).to eq(nil)
...@@ -85,41 +125,128 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do ...@@ -85,41 +125,128 @@ RSpec.describe Gitlab::HTTPConnectionAdapter do
end end
end end
context 'when local requests are allowed' do context 'when proxy settings are configured' do
let(:options) do
{
http_proxyaddr: 'https://proxy.org',
http_proxyport: 1557,
http_proxyuser: 'user',
http_proxypass: 'pass'
}
end
before do
stub_all_dns('https://proxy.org', ip_address: '166.84.12.54')
end
it 'sets up the proxy settings' do
expect(connection.proxy_address).to eq('https://166.84.12.54')
expect(connection.proxy_port).to eq(1557)
expect(connection.proxy_user).to eq('user')
expect(connection.proxy_pass).to eq('pass')
end
context 'when the address has path' do
before do
options[:http_proxyaddr] = 'https://proxy.org/path'
end
it 'sets up the proxy settings' do
expect(connection.proxy_address).to eq('https://166.84.12.54/path')
expect(connection.proxy_port).to eq(1557)
end
end
context 'when the port is in the address and port' do
before do
options[:http_proxyaddr] = 'https://proxy.org:1422'
end
it 'sets up the proxy settings' do
expect(connection.proxy_address).to eq('https://166.84.12.54')
expect(connection.proxy_port).to eq(1557)
end
context 'when the port is only in the address' do
before do
options[:http_proxyport] = nil
end
it 'sets up the proxy settings' do
expect(connection.proxy_address).to eq('https://166.84.12.54')
expect(connection.proxy_port).to eq(1422)
end
end
end
context 'when it is a request to local network' do
before do
options[:http_proxyaddr] = 'http://172.16.0.0/12'
end
it 'raises error' do
expect { subject }.to raise_error(
Gitlab::HTTP::BlockedUrlError,
"URL 'http://172.16.0.0:1557/12' is blocked: Requests to the local network are not allowed"
)
end
context 'when local request allowed' do
before do
options[:allow_local_requests] = true
end
it 'sets up the connection' do it 'sets up the connection' do
uri = URI('https://example.org') expect(connection.proxy_address).to eq('http://172.16.0.0/12')
expect(connection.proxy_port).to eq(1557)
end
end
end
connection = described_class.new(uri, allow_local_requests: true).connection context 'when it is a request to local address' do
before do
options[:http_proxyaddr] = 'http://127.0.0.1'
end
expect(connection).to be_a(Net::HTTP) it 'raises error' do
expect(connection.address).to eq('93.184.216.34') expect { subject }.to raise_error(
expect(connection.hostname_override).to eq('example.org') Gitlab::HTTP::BlockedUrlError,
expect(connection.addr_port).to eq('example.org') "URL 'http://127.0.0.1:1557' is blocked: Requests to localhost are not allowed"
expect(connection.port).to eq(443) )
end end
it 'sets up the connection when it is a local network' do context 'when local request allowed' do
uri = URI('http://172.16.0.0/12') before do
options[:allow_local_requests] = true
end
connection = described_class.new(uri, allow_local_requests: true).connection it 'sets up the connection' do
expect(connection.proxy_address).to eq('http://127.0.0.1')
expect(connection.proxy_port).to eq(1557)
end
end
end
expect(connection).to be_a(Net::HTTP) context 'when http(s) environment variable is set' do
expect(connection.address).to eq('172.16.0.0') before do
expect(connection.hostname_override).to be(nil) stub_env('https_proxy' => 'https://my.proxy')
expect(connection.addr_port).to eq('172.16.0.0')
expect(connection.port).to eq(80)
end end
it 'sets up the connection when it is localhost' do it 'sets up the connection' do
uri = URI('http://127.0.0.1') expect(connection.proxy_address).to eq('https://proxy.org')
expect(connection.proxy_port).to eq(1557)
end
end
connection = described_class.new(uri, allow_local_requests: true).connection context 'when DNS rebinding protection is disabled' do
before do
stub_application_setting(dns_rebinding_protection_enabled: false)
end
expect(connection).to be_a(Net::HTTP) it 'sets up the connection' do
expect(connection.address).to eq('127.0.0.1') expect(connection.proxy_address).to eq('https://proxy.org')
expect(connection.hostname_override).to be(nil) expect(connection.proxy_port).to eq(1557)
expect(connection.addr_port).to eq('127.0.0.1') end
expect(connection.port).to eq(80)
end end
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment