diff options
author | Tyler Ball <tyler-ball@users.noreply.github.com> | 2015-12-09 14:24:08 -0700 |
---|---|---|
committer | Tyler Ball <tyler-ball@users.noreply.github.com> | 2015-12-09 14:24:08 -0700 |
commit | 40bcd3accf755b739aeaea1cc325eebf43519d59 (patch) | |
tree | 5be428017e0883fdcaa19c74308bb73783270071 | |
parent | 010392858c2a3a036578b681085704ed1971ab21 (diff) | |
parent | 85d0407a16fa4dfc479d550a96355a6d11f4f551 (diff) | |
download | chef-40bcd3accf755b739aeaea1cc325eebf43519d59.tar.gz |
Merge pull request #4237 from chef/tball/proxifier
Enabling 'knife ssl check/fetch' commands to respect proxy environment variables and moving proxy environment variables export to Chef::Config
-rw-r--r-- | .travis.yml | 19 | ||||
-rw-r--r-- | chef-config/lib/chef-config/config.rb | 61 | ||||
-rw-r--r-- | chef-config/spec/unit/config_spec.rb | 108 | ||||
-rw-r--r-- | chef.gemspec | 2 | ||||
-rw-r--r-- | lib/chef/application.rb | 84 | ||||
-rw-r--r-- | lib/chef/application/apply.rb | 2 | ||||
-rw-r--r-- | lib/chef/knife.rb | 1 | ||||
-rw-r--r-- | lib/chef/knife/ssl_check.rb | 6 | ||||
-rw-r--r-- | lib/chef/knife/ssl_fetch.rb | 5 | ||||
-rw-r--r-- | lib/chef/mixin/proxified_socket.rb | 38 | ||||
-rw-r--r-- | spec/functional/application_spec.rb | 2 | ||||
-rw-r--r-- | spec/unit/application_spec.rb | 182 | ||||
-rw-r--r-- | spec/unit/knife/ssl_check_spec.rb | 4 | ||||
-rw-r--r-- | spec/unit/knife/ssl_fetch_spec.rb | 4 | ||||
-rw-r--r-- | spec/unit/mixin/proxified_socket_spec.rb | 94 |
15 files changed, 328 insertions, 284 deletions
diff --git a/.travis.yml b/.travis.yml index 95cdfdd9b7..78303d7591 100644 --- a/.travis.yml +++ b/.travis.yml @@ -118,22 +118,21 @@ matrix: - rvm: 2.2 sudo: required dist: trusty - os: linux - cache: before_install: - sudo apt-get update - - sudo apt-get -y install squid3 git + - sudo apt-get -y install squid3 git curl env: - - PROXY_TESTS_DIR=/tmp/proxy_tests - - PROXY_TESTS_REPO=$PROXY_TESTS_DIR/repo + global: + - PROXY_TESTS_DIR=proxy_tests/files/default/scripts + - PROXY_TESTS_REPO=$PROXY_TESTS_DIR/repo script: - bundle exec chef-client --version - git clone https://github.com/chef/proxy_tests.git - - cd proxy_tests - - bundle exec chef-client -z -o proxy_tests::render - #- sh /tmp/proxy_tests/setup.sh - - bundle exec sudo -E bash /tmp/proxy_tests/run_tests.sh chef_client \* \* /tmp/out.txt - after_script: cat /tmp/out.txt + - rvmsudo -E bundle exec bash $PROXY_TESTS_DIR/run_tests.sh chef_client \* \* /tmp/out.txt + after_script: + - cat /tmp/out.txt + - sudo cat /var/log/squid3/cache.log + - sudo cat /var/log/squid3/access.log allow_failures: - rvm: rbx diff --git a/chef-config/lib/chef-config/config.rb b/chef-config/lib/chef-config/config.rb index 49d775232d..113bf481ff 100644 --- a/chef-config/lib/chef-config/config.rb +++ b/chef-config/lib/chef-config/config.rb @@ -26,6 +26,7 @@ require 'chef-config/logger' require 'chef-config/windows' require 'chef-config/path_helper' require 'mixlib/shellout' +require 'uri' module ChefConfig @@ -710,6 +711,66 @@ module ChefConfig config_context :chefdk do end + configurable(:http_proxy) + configurable(:http_proxy_user) + configurable(:http_proxy_pass) + configurable(:https_proxy) + configurable(:https_proxy_user) + configurable(:https_proxy_pass) + configurable(:ftp_proxy) + configurable(:ftp_proxy_user) + configurable(:ftp_proxy_pass) + configurable(:no_proxy) + + # Public method that users should call to export proxies to the appropriate + # environment variables. This method should be called after the config file is + # parsed and loaded. + # TODO add some post-file-parsing logic that automatically calls this so + # users don't have to + def self.export_proxies + export_proxy("http", http_proxy, http_proxy_user, http_proxy_pass) if http_proxy + export_proxy("https", https_proxy, https_proxy_user, https_proxy_pass) if https_proxy + export_proxy("ftp", ftp_proxy, ftp_proxy_user, ftp_proxy_pass) if ftp_proxy + export_no_proxy("no_proxy", no_proxy) if no_proxy + end + + # Builds a proxy uri and exports it to the appropriate environment variables. Examples: + # http://username:password@hostname:port + # https://username@hostname:port + # ftp://hostname:port + # when + # scheme = "http", "https", or "ftp" + # hostport = hostname:port or scheme://hostname:port + # user = username + # pass = password + # @api private + def self.export_proxy(scheme, path, user, pass) + path = "#{scheme}://#{path}" unless path.include?('://') + # URI.split returns the following parts: + # [scheme, userinfo, host, port, registry, path, opaque, query, fragment] + parts = URI.split(URI.encode(path)) + # URI::Generic.build requires an integer for the port, but URI::split gives + # returns a string for the port. + parts[3] = parts[3].to_i if parts[3] + if user && !user.empty? + userinfo = URI.encode(URI.encode(user), '@:') + if pass + userinfo << ":#{URI.encode(URI.encode(pass), '@:')}" + end + parts[1] = userinfo + end + + path = URI::Generic.build(parts).to_s + ENV["#{scheme}_proxy".downcase] = path unless ENV["#{scheme}_proxy".downcase] + ENV["#{scheme}_proxy".upcase] = path unless ENV["#{scheme}_proxy".upcase] + end + + # @api private + def self.export_no_proxy(value) + ENV['no_proxy'] = value unless ENV['no_proxy'] + ENV['NO_PROXY'] = value unless ENV['NO_PROXY'] + end + # Chef requires an English-language UTF-8 locale to function properly. We attempt # to use the 'locale -a' command and search through a list of preferences until we # find one that we can use. On Ubuntu systems we should find 'C.UTF-8' and be diff --git a/chef-config/spec/unit/config_spec.rb b/chef-config/spec/unit/config_spec.rb index 4af5d4b7c7..0a3dca5b5a 100644 --- a/chef-config/spec/unit/config_spec.rb +++ b/chef-config/spec/unit/config_spec.rb @@ -571,6 +571,114 @@ RSpec.describe ChefConfig::Config do end end + describe "export_proxies" do + let(:http_proxy) { "http://localhost:7979" } + let(:https_proxy) { "https://localhost:7979" } + let(:ftp_proxy) { "ftp://localhost:7979" } + let(:proxy_user) { "http_user" } + let(:proxy_pass) { "http_pass" } + + context "when http_proxy, proxy_pass and proxy_user are set" do + before do + ChefConfig::Config.http_proxy = http_proxy + ChefConfig::Config.http_proxy_user = proxy_user + ChefConfig::Config.http_proxy_pass = proxy_pass + end + it "exports ENV['http_proxy']" do + expect(ENV).to receive(:[]=).with('http_proxy', "http://http_user:http_pass@localhost:7979") + expect(ENV).to receive(:[]=).with('HTTP_PROXY', "http://http_user:http_pass@localhost:7979") + ChefConfig::Config.export_proxies + end + end + + context "when https_proxy, proxy_pass and proxy_user are set" do + before do + ChefConfig::Config.https_proxy = https_proxy + ChefConfig::Config.https_proxy_user = proxy_user + ChefConfig::Config.https_proxy_pass = proxy_pass + end + it "exports ENV['https_proxy']" do + expect(ENV).to receive(:[]=).with('https_proxy', "https://http_user:http_pass@localhost:7979") + expect(ENV).to receive(:[]=).with('HTTPS_PROXY', "https://http_user:http_pass@localhost:7979") + ChefConfig::Config.export_proxies + end + end + + context "when ftp_proxy, proxy_pass and proxy_user are set" do + before do + ChefConfig::Config.ftp_proxy = ftp_proxy + ChefConfig::Config.ftp_proxy_user = proxy_user + ChefConfig::Config.ftp_proxy_pass = proxy_pass + end + it "exports ENV['ftp_proxy']" do + expect(ENV).to receive(:[]=).with('ftp_proxy', "ftp://http_user:http_pass@localhost:7979") + expect(ENV).to receive(:[]=).with('FTP_PROXY', "ftp://http_user:http_pass@localhost:7979") + ChefConfig::Config.export_proxies + end + end + + shared_examples "no user pass" do + it "does not populate the user or password" do + expect(ENV).to receive(:[]=).with('http_proxy', "http://localhost:7979") + expect(ENV).to receive(:[]=).with('HTTP_PROXY', "http://localhost:7979") + ChefConfig::Config.export_proxies + end + end + + context "when proxy_pass and proxy_user are passed as empty strings" do + before do + ChefConfig::Config.http_proxy = http_proxy + ChefConfig::Config.http_proxy_user = "" + ChefConfig::Config.http_proxy_pass = proxy_pass + end + include_examples "no user pass" + end + + context "when proxy_pass and proxy_user are not provided" do + before do + ChefConfig::Config.http_proxy = http_proxy + end + include_examples "no user pass" + end + + context "when the proxy is provided without a scheme" do + before do + ChefConfig::Config.http_proxy = "localhost:1111" + end + it "automatically adds the scheme to the proxy url" do + expect(ENV).to receive(:[]=).with('http_proxy', "http://localhost:1111") + expect(ENV).to receive(:[]=).with('HTTP_PROXY', "http://localhost:1111") + ChefConfig::Config.export_proxies + end + end + + shared_examples "no export" do + it "does not export any proxy settings" do + ChefConfig::Config.export_proxies + expect(ENV['http_proxy']).to eq(nil) + expect(ENV['https_proxy']).to eq(nil) + expect(ENV['ftp_proxy']).to eq(nil) + expect(ENV['no_proxy']).to eq(nil) + end + end + + context "when nothing is set" do + include_examples "no export" + end + + context "when all the users and passwords are set but no proxies are set" do + before do + ChefConfig::Config.http_proxy_user = proxy_user + ChefConfig::Config.http_proxy_pass = proxy_pass + ChefConfig::Config.https_proxy_user = proxy_user + ChefConfig::Config.https_proxy_pass = proxy_pass + ChefConfig::Config.ftp_proxy_user = proxy_user + ChefConfig::Config.ftp_proxy_pass = proxy_pass + end + include_examples "no export" + end + end + describe "allowing chefdk configuration outside of chefdk" do it "allows arbitrary settings in the chefdk config context" do diff --git a/chef.gemspec b/chef.gemspec index faa00e87d6..2e33ec6b4b 100644 --- a/chef.gemspec +++ b/chef.gemspec @@ -43,6 +43,8 @@ Gem::Specification.new do |s| s.add_dependency "syslog-logger", "~> 1.6" + s.add_dependency "proxifier", "~> 1.0" + s.add_development_dependency "rack" s.add_development_dependency "cheffish", "~> 1.1" diff --git a/lib/chef/application.rb b/lib/chef/application.rb index 970544c068..a2c415111e 100644 --- a/lib/chef/application.rb +++ b/lib/chef/application.rb @@ -17,7 +17,6 @@ # limitations under the License. require 'pp' -require 'uri' require 'socket' require 'chef/config' require 'chef/config_fetcher' @@ -47,7 +46,6 @@ class Chef def reconfigure configure_chef configure_logging - configure_proxy_environment_variables configure_encoding emit_warnings end @@ -85,6 +83,7 @@ class Chef def configure_chef parse_options load_config_file + Chef::Config.export_proxies end # Parse the config file @@ -180,14 +179,6 @@ class Chef end end - # Configure and set any proxy environment variables according to the config. - def configure_proxy_environment_variables - configure_http_proxy - configure_https_proxy - configure_ftp_proxy - configure_no_proxy - end - # Sets the default external encoding to UTF-8 (users can change this, but they shouldn't) def configure_encoding Encoding.default_external = Chef::Config[:ruby_encoding] @@ -302,79 +293,6 @@ class Chef Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2) end - # Set ENV['http_proxy'] - def configure_http_proxy - if http_proxy = Chef::Config[:http_proxy] - http_proxy_string = configure_proxy("http", http_proxy, - Chef::Config[:http_proxy_user], Chef::Config[:http_proxy_pass]) - env['http_proxy'] = http_proxy_string unless env['http_proxy'] - env['HTTP_PROXY'] = http_proxy_string unless env['HTTP_PROXY'] - end - end - - # Set ENV['https_proxy'] - def configure_https_proxy - if https_proxy = Chef::Config[:https_proxy] - https_proxy_string = configure_proxy("https", https_proxy, - Chef::Config[:https_proxy_user], Chef::Config[:https_proxy_pass]) - env['https_proxy'] = https_proxy_string unless env['https_proxy'] - env['HTTPS_PROXY'] = https_proxy_string unless env['HTTPS_PROXY'] - end - end - - # Set ENV['ftp_proxy'] - def configure_ftp_proxy - if ftp_proxy = Chef::Config[:ftp_proxy] - ftp_proxy_string = configure_proxy("ftp", ftp_proxy, - Chef::Config[:ftp_proxy_user], Chef::Config[:ftp_proxy_pass]) - env['ftp_proxy'] = ftp_proxy_string unless env['ftp_proxy'] - env['FTP_PROXY'] = ftp_proxy_string unless env['FTP_PROXY'] - end - end - - # Set ENV['no_proxy'] - def configure_no_proxy - if Chef::Config[:no_proxy] - env['no_proxy'] = Chef::Config[:no_proxy] unless env['no_proxy'] - env['NO_PROXY'] = Chef::Config[:no_proxy] unless env['NO_PROXY'] - end - end - - # Builds a proxy uri. Examples: - # http://username:password@hostname:port - # https://username@hostname:port - # ftp://hostname:port - # when - # scheme = "http", "https", or "ftp" - # hostport = hostname:port - # user = username - # pass = password - def configure_proxy(scheme, path, user, pass) - begin - path = "#{scheme}://#{path}" unless path.include?('://') - # URI.split returns the following parts: - # [scheme, userinfo, host, port, registry, path, opaque, query, fragment] - parts = URI.split(URI.encode(path)) - # URI::Generic.build requires an integer for the port, but URI::split gives - # returns a string for the port. - parts[3] = parts[3].to_i if parts[3] - if user - userinfo = URI.encode(URI.encode(user), '@:') - if pass - userinfo << ":#{URI.encode(URI.encode(pass), '@:')}" - end - parts[1] = userinfo - end - - return URI::Generic.build(parts).to_s - rescue URI::Error => e - # URI::Error messages generally include the offending string. Including a message - # for which proxy config item has the issue should help deduce the issue when - # the URI::Error message is vague. - raise Chef::Exceptions::BadProxyURI, "Cannot configure #{scheme} proxy. Does not comply with URI scheme. #{e.message}" - end - end - # This is a hook for testing def env ENV diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb index 4c559542f1..6a371c89c4 100644 --- a/lib/chef/application/apply.rb +++ b/lib/chef/application/apply.rb @@ -124,7 +124,7 @@ class Chef::Application::Apply < Chef::Application parse_options Chef::Config.merge!(config) configure_logging - configure_proxy_environment_variables + Chef::Config.export_proxies parse_json end diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index 6fa29bea16..5df24faa11 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -401,6 +401,7 @@ class Chef merge_configs apply_computed_config + Chef::Config.export_proxies # This has to be after apply_computed_config so that Mixlib::Log is configured Chef::Log.info("Using configuration from #{config[:config_file]}") if config[:config_file] end diff --git a/lib/chef/knife/ssl_check.rb b/lib/chef/knife/ssl_check.rb index d71eacfc7e..38b4d81bb3 100644 --- a/lib/chef/knife/ssl_check.rb +++ b/lib/chef/knife/ssl_check.rb @@ -29,6 +29,8 @@ class Chef require 'uri' require 'chef/http/ssl_policies' require 'openssl' + require 'chef/mixin/proxified_socket' + include Chef::Mixin::ProxifiedSocket end banner "knife ssl check [URL] (options)" @@ -75,7 +77,7 @@ class Chef def verify_peer_socket @verify_peer_socket ||= begin - tcp_connection = TCPSocket.new(host, port) + tcp_connection = proxified_socket(host, port) ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_connection, verify_peer_ssl_context) ssl_client.hostname = host ssl_client @@ -93,7 +95,7 @@ class Chef def noverify_socket @noverify_socket ||= begin - tcp_connection = TCPSocket.new(host, port) + tcp_connection = proxified_socket(host, port) OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context) end end diff --git a/lib/chef/knife/ssl_fetch.rb b/lib/chef/knife/ssl_fetch.rb index fd7d101fd8..0c1ab7ea7b 100644 --- a/lib/chef/knife/ssl_fetch.rb +++ b/lib/chef/knife/ssl_fetch.rb @@ -28,6 +28,8 @@ class Chef require 'socket' require 'uri' require 'openssl' + require 'chef/mixin/proxified_socket' + include Chef::Mixin::ProxifiedSocket end banner "knife ssl fetch [URL] (options)" @@ -71,7 +73,7 @@ class Chef end def remote_cert_chain - tcp_connection = TCPSocket.new(host, port) + tcp_connection = proxified_socket(host, port) shady_ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_ssl_context) shady_ssl_connection.connect shady_ssl_connection.peer_cert_chain @@ -155,4 +157,3 @@ TRUST_TRUST end end end - diff --git a/lib/chef/mixin/proxified_socket.rb b/lib/chef/mixin/proxified_socket.rb new file mode 100644 index 0000000000..3fda0083c3 --- /dev/null +++ b/lib/chef/mixin/proxified_socket.rb @@ -0,0 +1,38 @@ +# Author:: Tyler Ball (<tball@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'proxifier' + +class Chef + module Mixin + module ProxifiedSocket + + # This looks at the environment variables and leverages Proxifier to + # make the TCPSocket respect ENV['https_proxy'] or ENV['http_proxy'] if + # they are present + def proxified_socket(host, port) + proxy = ENV['https_proxy'] || ENV['http_proxy'] || false + if proxy + Proxifier.Proxy(proxy, no_proxy: ENV['no_proxy']).open(host, port) + else + TCPSocket.new(host, port) + end + end + + end + end +end diff --git a/spec/functional/application_spec.rb b/spec/functional/application_spec.rb index 00ff0f702a..fe656dca60 100644 --- a/spec/functional/application_spec.rb +++ b/spec/functional/application_spec.rb @@ -42,7 +42,7 @@ describe Chef::Application do Chef::Config[:ftp_proxy] = nil Chef::Config[:no_proxy] = nil - @app.configure_proxy_environment_variables + Chef::Config.export_proxies end it "saves built proxy to ENV which shell_out can use" do diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb index f5a2c72aa0..20b7e3a506 100644 --- a/spec/unit/application_spec.rb +++ b/spec/unit/application_spec.rb @@ -39,7 +39,6 @@ describe Chef::Application do @app = Chef::Application.new allow(@app).to receive(:configure_chef).and_return(true) allow(@app).to receive(:configure_logging).and_return(true) - allow(@app).to receive(:configure_proxy_environment_variables).and_return(true) end it "should configure chef" do @@ -52,11 +51,6 @@ describe Chef::Application do @app.reconfigure end - it "should configure environment variables" do - expect(@app).to receive(:configure_proxy_environment_variables).and_return(true) - @app.reconfigure - end - it 'should not receive set_specific_recipes' do expect(@app).to_not receive(:set_specific_recipes) @app.reconfigure @@ -101,6 +95,7 @@ describe Chef::Application do @app = Chef::Application.new #Chef::Config.stub(:merge!).and_return(true) allow(@app).to receive(:parse_options).and_return(true) + expect(Chef::Config).to receive(:export_proxies).and_return(true) end it "should parse the commandline options" do @@ -245,181 +240,6 @@ describe Chef::Application do end end - describe "when configuring environment variables" do - def configure_proxy_environment_variables_stubs - allow(@app).to receive(:configure_http_proxy).and_return(true) - allow(@app).to receive(:configure_https_proxy).and_return(true) - allow(@app).to receive(:configure_ftp_proxy).and_return(true) - allow(@app).to receive(:configure_no_proxy).and_return(true) - end - - shared_examples_for "setting ENV['http_proxy']" do - before do - Chef::Config[:http_proxy] = http_proxy - end - - it "should set ENV['http_proxy']" do - @app.configure_proxy_environment_variables - expect(@env['http_proxy']).to eq("#{scheme}://#{address}:#{port}") - end - - it "should set ENV['HTTP_PROXY']" do - @app.configure_proxy_environment_variables - expect(@env['HTTP_PROXY']).to eq("#{scheme}://#{address}:#{port}") - end - - describe "when Chef::Config[:http_proxy_user] is set" do - before do - Chef::Config[:http_proxy_user] = "username" - end - - it "should set ENV['http_proxy'] with the username" do - @app.configure_proxy_environment_variables - expect(@env['http_proxy']).to eq("#{scheme}://username@#{address}:#{port}") - expect(@env['HTTP_PROXY']).to eq("#{scheme}://username@#{address}:#{port}") - end - - context "when :http_proxy_user contains '@' and/or ':'" do - before do - Chef::Config[:http_proxy_user] = "my:usern@me" - end - - it "should set ENV['http_proxy'] with the escaped username" do - @app.configure_proxy_environment_variables - expect(@env['http_proxy']).to eq("#{scheme}://my%3Ausern%40me@#{address}:#{port}") - expect(@env['HTTP_PROXY']).to eq("#{scheme}://my%3Ausern%40me@#{address}:#{port}") - end - end - - describe "when Chef::Config[:http_proxy_pass] is set" do - before do - Chef::Config[:http_proxy_pass] = "password" - end - - it "should set ENV['http_proxy'] with the password" do - @app.configure_proxy_environment_variables - expect(@env['http_proxy']).to eq("#{scheme}://username:password@#{address}:#{port}") - expect(@env['HTTP_PROXY']).to eq("#{scheme}://username:password@#{address}:#{port}") - end - - context "when :http_proxy_pass contains '@' and/or ':'" do - before do - Chef::Config[:http_proxy_pass] = ":P@ssword101" - end - - it "should set ENV['http_proxy'] with the escaped password" do - @app.configure_proxy_environment_variables - expect(@env['http_proxy']).to eq("#{scheme}://username:%3AP%40ssword101@#{address}:#{port}") - expect(@env['HTTP_PROXY']).to eq("#{scheme}://username:%3AP%40ssword101@#{address}:#{port}") - end - end - end - end - - describe "when Chef::Config[:http_proxy_pass] is set (but not Chef::Config[:http_proxy_user])" do - before do - Chef::Config[:http_proxy_user] = nil - Chef::Config[:http_proxy_pass] = "password" - end - - it "should set ENV['http_proxy']" do - @app.configure_proxy_environment_variables - expect(@env['http_proxy']).to eq("#{scheme}://#{address}:#{port}") - expect(@env['HTTP_PROXY']).to eq("#{scheme}://#{address}:#{port}") - end - end - end - - describe "when configuring ENV['http_proxy']" do - before do - @env = {} - allow(@app).to receive(:env).and_return(@env) - - allow(@app).to receive(:configure_https_proxy).and_return(true) - allow(@app).to receive(:configure_ftp_proxy).and_return(true) - allow(@app).to receive(:configure_no_proxy).and_return(true) - end - - describe "when Chef::Config[:http_proxy] is not set" do - before do - Chef::Config[:http_proxy] = nil - end - - it "should not set ENV['http_proxy']" do - @app.configure_proxy_environment_variables - expect(@env).to eq({}) - end - end - - describe "when Chef::Config[:http_proxy] is set" do - context "when given an FQDN" do - let(:scheme) { "http" } - let(:address) { "proxy.example.org" } - let(:port) { 8080 } - let(:http_proxy) { "#{scheme}://#{address}:#{port}" } - - it_should_behave_like "setting ENV['http_proxy']" - end - - context "when given an HTTPS URL" do - let(:scheme) { "https" } - let(:address) { "proxy.example.org" } - let(:port) { 8080 } - let(:http_proxy) { "#{scheme}://#{address}:#{port}" } - - it_should_behave_like "setting ENV['http_proxy']" - end - - context "when given an IP" do - let(:scheme) { "http" } - let(:address) { "127.0.0.1" } - let(:port) { 22 } - let(:http_proxy) { "#{scheme}://#{address}:#{port}" } - - it_should_behave_like "setting ENV['http_proxy']" - end - - context "when given an IPv6" do - let(:scheme) { "http" } - let(:address) { "[2001:db8::1]" } - let(:port) { 80 } - let(:http_proxy) { "#{scheme}://#{address}:#{port}" } - - it_should_behave_like "setting ENV['http_proxy']" - end - - context "when given without including http://" do - let(:scheme) { "http" } - let(:address) { "proxy.example.org" } - let(:port) { 8181 } - let(:http_proxy) { "#{address}:#{port}" } - - it_should_behave_like "setting ENV['http_proxy']" - end - - context "when given the full proxy in :http_proxy only" do - before do - Chef::Config[:http_proxy] = "http://username:password@proxy.example.org:2222" - Chef::Config[:http_proxy_user] = nil - Chef::Config[:http_proxy_pass] = nil - end - - it "should set ENV['http_proxy']" do - @app.configure_proxy_environment_variables - expect(@env['http_proxy']).to eq(Chef::Config[:http_proxy]) - end - end - - context "when the config options aren't URI compliant" do - it "raises Chef::Exceptions::BadProxyURI" do - Chef::Config[:http_proxy] = "http://proxy.bad_example.org/:8080" - expect { @app.configure_proxy_environment_variables }.to raise_error(Chef::Exceptions::BadProxyURI) - end - end - end - end - end - describe "class method: fatal!" do before do allow(STDERR).to receive(:puts).with("FATAL: blah").and_return(true) diff --git a/spec/unit/knife/ssl_check_spec.rb b/spec/unit/knife/ssl_check_spec.rb index a9d1145f34..feeb85c8af 100644 --- a/spec/unit/knife/ssl_check_spec.rb +++ b/spec/unit/knife/ssl_check_spec.rb @@ -145,7 +145,7 @@ E let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket) } before do - expect(TCPSocket).to receive(:new).with("foo.example.com", 8443).and_return(tcp_socket) + expect(ssl_check).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket) expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_check.verify_peer_ssl_context).and_return(ssl_socket) end @@ -183,7 +183,7 @@ E before do @old_signal = trap(:INT, "DEFAULT") - expect(TCPSocket).to receive(:new). + expect(ssl_check).to receive(:proxified_socket). with("foo.example.com", 8443). and_return(tcp_socket_for_debug) expect(OpenSSL::SSL::SSLSocket).to receive(:new). diff --git a/spec/unit/knife/ssl_fetch_spec.rb b/spec/unit/knife/ssl_fetch_spec.rb index cd0e423459..5982ed9470 100644 --- a/spec/unit/knife/ssl_fetch_spec.rb +++ b/spec/unit/knife/ssl_fetch_spec.rb @@ -139,7 +139,7 @@ E context "when the TLS connection is successful" do before do - expect(TCPSocket).to receive(:new).with("foo.example.com", 8443).and_return(tcp_socket) + expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 8443).and_return(tcp_socket) expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket) expect(ssl_socket).to receive(:connect) expect(ssl_socket).to receive(:peer_cert_chain).and_return([self_signed_crt]) @@ -161,7 +161,7 @@ E let(:unknown_protocol_error) { OpenSSL::SSL::SSLError.new("SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol") } before do - expect(TCPSocket).to receive(:new).with("foo.example.com", 80).and_return(tcp_socket) + expect(ssl_fetch).to receive(:proxified_socket).with("foo.example.com", 80).and_return(tcp_socket) expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(tcp_socket, ssl_fetch.noverify_peer_ssl_context).and_return(ssl_socket) expect(ssl_socket).to receive(:connect).and_raise(unknown_protocol_error) diff --git a/spec/unit/mixin/proxified_socket_spec.rb b/spec/unit/mixin/proxified_socket_spec.rb new file mode 100644 index 0000000000..88f71ae48b --- /dev/null +++ b/spec/unit/mixin/proxified_socket_spec.rb @@ -0,0 +1,94 @@ +# +# Author:: Tyler Ball (<tball@chef.io>) +# Copyright:: Copyright (c) 2014 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" +require "chef/mixin/proxified_socket" +require "proxifier/proxy" + +class TestProxifiedSocket + include Chef::Mixin::ProxifiedSocket +end + +describe Chef::Mixin::ProxifiedSocket do + + before do + @original_env = ENV.to_hash + end + + after do + ENV.clear + ENV.update(@original_env) + end + + let(:host) { "host" } + let(:port) { 7979 } + let(:test_instance) { TestProxifiedSocket.new } + let(:socket_double) { instance_double(TCPSocket)} + let(:proxifier_double) { instance_double(Proxifier::Proxy) } + let(:http_uri) { "http://somehost:1" } + let(:https_uri) { "https://somehost:1" } + let(:no_proxy_spec) { nil } + + shared_examples "proxified socket" do + it "wraps the Socket in a Proxifier::Proxy" do + expect(Proxifier).to receive(:Proxy).with(proxy_uri, no_proxy: no_proxy_spec).and_return(proxifier_double) + expect(proxifier_double).to receive(:open).with(host, port).and_return(socket_double) + expect(test_instance.proxified_socket(host, port)).to eq(socket_double) + end + end + + context "when no proxy is set" do + it "returns a plain TCPSocket" do + expect(TCPSocket).to receive(:new).with(host, port).and_return(socket_double) + expect(test_instance.proxified_socket(host, port)).to eq(socket_double) + end + end + + context "when https_proxy is set" do + before do + # I'm purposefully setting both of these because we prefer the https + # variable + ENV['https_proxy'] = https_uri + ENV['http_proxy'] = http_uri + end + + let(:proxy_uri) { https_uri } + include_examples "proxified socket" + + context "when no_proxy is set" do + # This is testing that no_proxy is also provided to Proxified + # when it is set + before do + ENV['no_proxy'] = no_proxy_spec + end + + let(:no_proxy_spec) { "somehost1,somehost2" } + include_examples "proxified socket" + end + end + + context "when http_proxy is set" do + before do + ENV['http_proxy'] = http_uri + end + + let(:proxy_uri) { http_uri } + include_examples "proxified socket" + end + +end |