Skip to content

Commit

Permalink
Allow cloud metadata to ignore proxy settings
Browse files Browse the repository at this point in the history
Prior to this commit, if the http_proxy environment variable was set for
Net::HTTP, cloud resolvers (ec2, GCE, Azure) would fail to resolve
metadata.

This commit adds a new positional argument to
Facter::Util::Resolvers::Http.get_request and #put_request to allow
those resolvers to disable proxy settings.
  • Loading branch information
mhashizume committed Apr 12, 2024
1 parent dd60cc2 commit 35963ca
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 143 deletions.
8 changes: 8 additions & 0 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ Style/Documentation:
- 'spec_integration/**/*'
- 'scripts/*'
- 'install.rb'

# While it would be preferable to use a keyword argument for the proxy setting in #get_request and #put_request, if we
# add keyword arguments to those methods Ruby < 3 misinterprets earlier positional arguments as a keyword arguments.
# This is because those positional arguments are hashes that use symbols as keys.
# TODO: revisit this after we drop Ruby < 3 support.
Style/OptionalBooleanParameter:
Exclude:
- 'lib/facter/util/resolvers/http.rb'
2 changes: 1 addition & 1 deletion lib/facter/resolvers/az.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def read_facts(fact_name)

def get_data_from(url)
headers = { Metadata: 'true' }
Facter::Util::Resolvers::Http.get_request(url, headers, { session: determine_session_timeout })
Facter::Util::Resolvers::Http.get_request(url, headers, { session: determine_session_timeout }, false)
end

def determine_session_timeout
Expand Down
2 changes: 1 addition & 1 deletion lib/facter/resolvers/ec2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def build_path_component(line)
def get_data_from(url)
headers = {}
headers['X-aws-ec2-metadata-token'] = v2_token if v2_token
Facter::Util::Resolvers::Http.get_request(url, headers, { session: determine_session_timeout })
Facter::Util::Resolvers::Http.get_request(url, headers, { session: determine_session_timeout }, false)
end

def determine_session_timeout
Expand Down
2 changes: 1 addition & 1 deletion lib/facter/resolvers/gce.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def read_facts(fact_name)
end

def query_for_metadata
gce_data = extract_to_hash(Facter::Util::Resolvers::Http.get_request(METADATA_URL, HEADERS))
gce_data = extract_to_hash(Facter::Util::Resolvers::Http.get_request(METADATA_URL, HEADERS, false))
parse_instance(gce_data)

gce_data.empty? ? nil : gce_data
Expand Down
26 changes: 18 additions & 8 deletions lib/facter/util/resolvers/http.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,30 @@ class << self
# Defaults to an empty hash.
# @param timeouts [Hash] Values for the session and connection
# timeouts.
# @param proxy [Boolean] Whether to use proxy settings when calling
# Net::HTTP.new. Defaults to true.
# @returns [String] the response body if the response code is 200.
# If the response code is not 200, an empty string is returned.
# @example
# get_request('https://example.com', { "Accept": 'application/json' }, { session: 2.4, connection: 5 })
def get_request(url, headers = {}, timeouts = {})
make_request(url, headers, timeouts, 'GET')
def get_request(url, headers = {}, timeouts = {}, proxy = true)
make_request(url, headers, timeouts, 'GET', proxy)
end

# Makes a PUT HTTP request and returns its response
# @param (see #get_request)
# @return (see #get_request)
def put_request(url, headers = {}, timeouts = {})
make_request(url, headers, timeouts, 'PUT')
def put_request(url, headers = {}, timeouts = {}, proxy = true)
make_request(url, headers, timeouts, 'PUT', proxy)
end

private

def make_request(url, headers, timeouts, request_type)
def make_request(url, headers, timeouts, request_type, proxy)
require 'net/http'

uri = URI.parse(url)
http = http_obj(uri, timeouts)
http = http_obj(uri, timeouts, proxy)
request = request_obj(headers, uri, request_type)

# The Windows implementation of sockets does not respect net/http
Expand All @@ -57,8 +59,16 @@ def make_request(url, headers, timeouts, request_type)
''
end

def http_obj(parsed_url, timeouts)
http = Net::HTTP.new(parsed_url.host)
def http_obj(parsed_url, timeouts, proxy)
# If get_request or put_request are called and set proxy to false,
# manually set Net::HTTP.new's p_addr (proxy address) positional
# argument to nil to override anywhere else a proxy may be set
# (e.g. the http_proxy environment variable).
http = if proxy
Net::HTTP.new(parsed_url.host)
else
Net::HTTP.new(parsed_url.host, 80, nil)
end
http.read_timeout = timeouts[:session] || SESSION_TIMEOUT
http.open_timeout = timeouts[:connection] || CONNECTION_TIMEOUT

Expand Down
14 changes: 13 additions & 1 deletion spec/facter/resolvers/az_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

before do
allow(Facter::Util::Resolvers::Http).to receive(:get_request)
.with(uri, { Metadata: 'true' }, { session: 5 }).and_return(output)
.with(uri, { Metadata: 'true' }, { session: 5 }, false).and_return(output)
az.instance_variable_set(:@log, log_spy)
end

Expand All @@ -24,6 +24,18 @@
end
end

context "when a proxy is set with ENV['http_proxy']" do
before do
stub_const('ENV', { 'http_proxy' => 'http://example.com' })
end

let(:output) { '{"azEnvironment":"AzurePublicCloud"}' }

it 'returns az metadata' do
expect(az.resolve(:metadata)).to eq({ 'azEnvironment' => 'AzurePublicCloud' })
end
end

context 'when an exception is thrown' do
let(:output) { '' }

Expand Down
12 changes: 12 additions & 0 deletions spec/facter/resolvers/ec2_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,16 @@
expect(ec2.resolve(:userdata)).to eql(expected_str)
end
end

context "when a proxy is set with ENV['http_proxy']" do
before do
stub_const('ENV', { 'http_proxy' => 'http://example.com' })
stub_request(:put, token_uri).to_return(status: 200, body: token)
end

let(:headers) { { 'Accept' => '*/*' } }
let(:token) { 'v2_token' }

it_behaves_like 'ec2'
end
end
Loading

0 comments on commit 35963ca

Please sign in to comment.