summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock25
-rw-r--r--lib/chef/http.rb39
-rw-r--r--lib/chef/http/api_versions.rb6
-rw-r--r--lib/chef/http/authenticator.rb21
-rw-r--r--lib/chef/mixin/versioned_api.rb17
-rw-r--r--lib/chef/server_api_versions.rb4
-rw-r--r--omnibus/config/software/chef.rb2
-rw-r--r--spec/spec_helper.rb3
-rw-r--r--spec/unit/http/api_versions_spec.rb12
-rw-r--r--spec/unit/http/authenticator_spec.rb20
-rw-r--r--spec/unit/mixin/versioned_api_spec.rb21
12 files changed, 147 insertions, 27 deletions
diff --git a/Gemfile b/Gemfile
index 416927bf3a..49fc34986c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -22,8 +22,8 @@ gem "cheffish" # required for rspec tests
group(:omnibus_package) do
gem "appbundler"
gem "rb-readline"
- # CVE-2016-4658 https://github.com/sparklemotion/nokogiri/issues/1615
- gem "nokogiri", ">= 1.7.1"
+ # nokogiri has no ruby-2.4 version for windows so it cannot go into our Gemfile.lock
+ # gem "nokogiri", ">= 1.7.1"
end
group(:omnibus_package, :pry) do
diff --git a/Gemfile.lock b/Gemfile.lock
index 9029de0096..d5652b17eb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,6 +1,6 @@
GIT
remote: https://github.com/chef/chef-server
- revision: aa0ee4ec5538ef5f4842b3ea0bd089714c8d2c72
+ revision: 40c201d434ec14fe8985259b4fed9f81f3176691
specs:
oc-chef-pedant (2.2.0)
activesupport (>= 4.2.7.1, < 6.0)
@@ -54,19 +54,19 @@ GIT
GIT
remote: https://github.com/poise/halite.git
- revision: b0f1372ea7710e47b52c8c843597d21aaed5ebf6
+ revision: fc1f42ac129865ffe08b7fb47fd366abe9dbf29c
specs:
- halite (1.4.1.pre)
+ halite (1.5.1.pre)
bundler
chef (>= 12.0, < 14.0)
- stove (~> 4.0)
+ stove (~> 5.0)
thor
GIT
remote: https://github.com/poise/poise-boiler.git
- revision: dedd08087d65830e7c7dd0d6c8daa964cfb33999
+ revision: d547f77ca7335af7943f681e1d0dd68c825cf429
specs:
- poise-boiler (1.13.3.pre)
+ poise-boiler (1.14.1.pre)
bundler
chefspec (~> 5.0)
codeclimate-test-reporter (~> 0.4)
@@ -205,13 +205,13 @@ GEM
mixlib-cli (~> 1.4)
artifactory (2.8.1)
ast (2.3.0)
- aws-sdk (2.8.13)
- aws-sdk-resources (= 2.8.13)
- aws-sdk-core (2.8.13)
+ aws-sdk (2.8.14)
+ aws-sdk-resources (= 2.8.14)
+ aws-sdk-core (2.8.14)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
- aws-sdk-resources (2.8.13)
- aws-sdk-core (= 2.8.13)
+ aws-sdk-resources (2.8.14)
+ aws-sdk-core (= 2.8.14)
aws-sigv4 (1.0.0)
backports (3.7.0)
binding_of_caller (0.7.2)
@@ -477,7 +477,7 @@ GEM
net-ssh (>= 2.7, < 5.0)
net-telnet
sfl
- stove (4.1.1)
+ stove (5.0.0)
chef-api (~> 0.5)
logify (~> 0.2)
syslog-logger (1.6.8)
@@ -581,7 +581,6 @@ DEPENDENCIES
knife-windows
mixlib-install
netrc
- nokogiri (>= 1.7.1)
oc-chef-pedant!
octokit
ohai!
diff --git a/lib/chef/http.rb b/lib/chef/http.rb
index 12acae953c..c741dcca97 100644
--- a/lib/chef/http.rb
+++ b/lib/chef/http.rb
@@ -142,13 +142,26 @@ class Chef
# Makes an HTTP request to +path+ with the given +method+, +headers+, and
# +data+ (if applicable).
def request(method, path, headers = {}, data = false)
+ http_attempts ||= 0
url = create_url(path)
method, url, headers, data = apply_request_middleware(method, url, headers, data)
response, rest_request, return_value = send_http_request(method, url, headers, data)
response, rest_request, return_value = apply_response_middleware(response, rest_request, return_value)
+
response.error! unless success_response?(response)
return_value
+
+ rescue Net::HTTPServerException => e
+ http_attempts += 1
+ response = e.response
+ if response.kind_of?(Net::HTTPNotAcceptable) && version_retries - http_attempts > 0
+ Chef::Log.debug("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}")
+ sleep(http_retry_delay)
+ retry
+ else
+ raise
+ end
rescue Exception => exception
log_failed_request(response, return_value) unless response.nil?
@@ -159,6 +172,7 @@ class Chef
end
def streaming_request_with_progress(path, headers = {}, &progress_block)
+ http_attempts ||= 0
url = create_url(path)
response, rest_request, return_value = nil, nil, nil
tempfile = nil
@@ -177,6 +191,16 @@ class Chef
response.error!
end
tempfile
+ rescue Net::HTTPServerException => e
+ http_attempts += 1
+ response = e.response
+ if response.kind_of?(Net::HTTPNotAcceptable) && version_retries - http_attempts > 0
+ Chef::Log.debug("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}")
+ sleep(http_retry_delay)
+ retry
+ else
+ raise
+ end
rescue Exception => e
log_failed_request(response, return_value) unless response.nil?
if e.respond_to?(:chef_rest_request=)
@@ -195,6 +219,7 @@ class Chef
# @yield [tempfile] block to process the tempfile
# @yieldparams [tempfile<Tempfile>] tempfile
def streaming_request(path, headers = {})
+ http_attempts ||= 0
url = create_url(path)
response, rest_request, return_value = nil, nil, nil
tempfile = nil
@@ -222,6 +247,16 @@ class Chef
end
end
tempfile
+ rescue Net::HTTPServerException => e
+ http_attempts += 1
+ response = e.response
+ if response.kind_of?(Net::HTTPNotAcceptable) && version_retries - http_attempts > 0
+ Chef::Log.debug("Negotiating protocol version with #{url}, retry #{http_attempts}/#{version_retries}")
+ sleep(http_retry_delay)
+ retry
+ else
+ raise
+ end
rescue Exception => e
log_failed_request(response, return_value) unless response.nil?
if e.respond_to?(:chef_rest_request=)
@@ -413,6 +448,10 @@ class Chef
end
end
+ def version_retries
+ @version_retries ||= options[:version_class].possible_requests
+ end
+
# @api private
def http_retry_delay
config[:http_retry_delay]
diff --git a/lib/chef/http/api_versions.rb b/lib/chef/http/api_versions.rb
index e164da262d..674d8f85a7 100644
--- a/lib/chef/http/api_versions.rb
+++ b/lib/chef/http/api_versions.rb
@@ -16,6 +16,7 @@
#
require "chef/server_api_versions"
+require "chef/json_compat"
class Chef
class HTTP
@@ -31,8 +32,11 @@ class Chef
end
def handle_response(http_response, rest_request, return_value)
+ if http_response.code == "406"
+ ServerAPIVersions.instance.reset!
+ end
if http_response.key?("x-ops-server-api-version")
- ServerAPIVersions.instance.set_versions(http_response["x-ops-server-api-version"])
+ ServerAPIVersions.instance.set_versions(JSONCompat.parse(http_response["x-ops-server-api-version"]))
end
[http_response, rest_request, return_value]
end
diff --git a/lib/chef/http/authenticator.rb b/lib/chef/http/authenticator.rb
index 84065bf816..65367af107 100644
--- a/lib/chef/http/authenticator.rb
+++ b/lib/chef/http/authenticator.rb
@@ -30,6 +30,8 @@ class Chef
attr_reader :raw_key
attr_reader :attr_names
attr_reader :auth_credentials
+ attr_reader :version_class
+ attr_reader :api_version
attr_accessor :sign_request
@@ -39,15 +41,12 @@ class Chef
@signing_key_filename = opts[:signing_key_filename]
@key = load_signing_key(opts[:signing_key_filename], opts[:raw_key])
@auth_credentials = AuthCredentials.new(opts[:client_name], @key)
- if opts[:api_version]
- @api_version = opts[:api_version]
- else
- @api_version = DEFAULT_SERVER_API_VERSION
- end
+ @version_class = opts[:version_class]
+ @api_version = opts[:api_version]
end
def handle_request(method, url, headers = {}, data = false)
- headers["X-Ops-Server-API-Version"] = @api_version
+ headers["X-Ops-Server-API-Version"] = request_version
headers.merge!(authentication_headers(method, url, data, headers)) if sign_requests?
[method, url, headers, data]
end
@@ -64,6 +63,16 @@ class Chef
[http_response, rest_request, return_value]
end
+ def request_version
+ if version_class
+ version_class.best_request_version
+ elsif api_version
+ api_version
+ else
+ DEFAULT_SERVER_API_VERSION
+ end
+ end
+
def sign_requests?
auth_credentials.sign_requests? && @sign_request
end
diff --git a/lib/chef/mixin/versioned_api.rb b/lib/chef/mixin/versioned_api.rb
index 9c2f2f4cdb..17c9838d29 100644
--- a/lib/chef/mixin/versioned_api.rb
+++ b/lib/chef/mixin/versioned_api.rb
@@ -40,11 +40,15 @@ class Chef
end
def versioned_api_class
+ get_class_for(:max_server_version)
+ end
+
+ def get_class_for(type)
versioned_interfaces.select do |klass|
version = klass.send(:minimum_api_version)
# min and max versions will be nil if we've not made a request to the server yet,
# in which case we'll just start with the highest version and see what happens
- ServerAPIVersions.instance.min_server_version.nil? || (version >= ServerAPIVersions.instance.min_server_version && version <= ServerAPIVersions.instance.max_server_version)
+ ServerAPIVersions.instance.min_server_version.nil? || (version >= ServerAPIVersions.instance.min_server_version && version <= ServerAPIVersions.instance.send(type))
end
.sort { |a, b| a.send(:minimum_api_version) <=> b.send(:minimum_api_version) }
.last
@@ -59,6 +63,17 @@ class Chef
module_eval(str, __FILE__, line_no)
end
+ # When teeing up an HTTP request, we need to be able to ask which API version we should use.
+ # Something in Net::HTTP seems to expect to strip headers, so we return this as a string.
+ def best_request_version
+ klass = get_class_for(:max_server_version)
+ klass.minimum_api_version.to_s
+ end
+
+ def possible_requests
+ versioned_interfaces.length
+ end
+
def new(*args)
object = versioned_api_class.allocate
object.send(:initialize, *args)
diff --git a/lib/chef/server_api_versions.rb b/lib/chef/server_api_versions.rb
index 68dfd5ac90..2a4d0e6a5b 100644
--- a/lib/chef/server_api_versions.rb
+++ b/lib/chef/server_api_versions.rb
@@ -26,11 +26,11 @@ class Chef
end
def min_server_version
- !@versions.nil? ? @versions["min_version"] : nil
+ !@versions.nil? ? Integer(@versions["min_version"]) : nil
end
def max_server_version
- !@versions.nil? ? @versions["max_version"] : nil
+ !@versions.nil? ? Integer(@versions["max_version"]) : nil
end
def reset!
diff --git a/omnibus/config/software/chef.rb b/omnibus/config/software/chef.rb
index 4f7319bda1..c0af403b20 100644
--- a/omnibus/config/software/chef.rb
+++ b/omnibus/config/software/chef.rb
@@ -39,7 +39,7 @@ dependency "bundler"
# Worst offenders first to take best advantage of cache:
dependency "chef-gem-ffi-yajl"
dependency "chef-gem-ohai"
-dependency "chef-gem-nokogiri"
+dependency "chef-gem-nokogiri" unless windows?
dependency "chef-gem-libyajl2"
dependency "chef-gem-ruby-prof"
dependency "chef-gem-byebug"
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 50ad2a3fb6..8e1faa3060 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -70,6 +70,9 @@ require "chef/chef_fs/file_system_cache"
require "chef/api_client_v1"
+require "chef/mixin/versioned_api"
+require "chef/server_api_versions"
+
if ENV["CHEF_FIPS"] == "1"
Chef::Config.init_openssl
end
diff --git a/spec/unit/http/api_versions_spec.rb b/spec/unit/http/api_versions_spec.rb
index 79c97a1b69..91d46763c2 100644
--- a/spec/unit/http/api_versions_spec.rb
+++ b/spec/unit/http/api_versions_spec.rb
@@ -38,13 +38,14 @@ describe Chef::HTTP::APIVersions do
let(:response_body) { "Thanks for checking in." }
let(:response_headers) do
{
- "x-ops-server-api-version" => { "min_version" => 0, "max_version" => 2 },
+ "x-ops-server-api-version" => { "min_version" => 0, "max_version" => 2 }.to_json,
}
end
let(:response) do
m = double("HttpResponse", :body => response_body)
allow(m).to receive(:key?).with("x-ops-server-api-version").and_return(true)
+ allow(m).to receive(:code).and_return(return_value)
allow(m).to receive(:[]) do |key|
response_headers[key]
end
@@ -66,4 +67,13 @@ describe Chef::HTTP::APIVersions do
run_api_version_handler
expect(Chef::ServerAPIVersions.instance.min_server_version).to eq(0)
end
+
+ context "with an unacceptable api version" do
+ let (:return_value) { "406" }
+ it "resets the list of supported versions" do
+ Chef::ServerAPIVersions.instance.set_versions({ "min_version" => 1, "max_version" => 3 })
+ run_api_version_handler
+ expect(Chef::ServerAPIVersions.instance.min_server_version).to eq(0)
+ end
+ end
end
diff --git a/spec/unit/http/authenticator_spec.rb b/spec/unit/http/authenticator_spec.rb
index 7fd2bdc821..5de39523cf 100644
--- a/spec/unit/http/authenticator_spec.rb
+++ b/spec/unit/http/authenticator_spec.rb
@@ -38,6 +38,26 @@ describe Chef::HTTP::Authenticator do
to include({ "X-Ops-Server-API-Version" => Chef::HTTP::Authenticator::DEFAULT_SERVER_API_VERSION })
end
+ context "when version_class is provided" do
+ class V0Class; extend Chef::Mixin::VersionedAPI; minimum_api_version 0; end
+ class V2Class; extend Chef::Mixin::VersionedAPI; minimum_api_version 2; end
+
+ class AuthFactoryClass
+ extend Chef::Mixin::VersionedAPIFactory
+ add_versioned_api_class V0Class
+ add_versioned_api_class V2Class
+ end
+
+ let(:class_instance) { Chef::HTTP::Authenticator.new({ version_class: AuthFactoryClass }) }
+
+ it "uses it to select the correct http version" do
+ Chef::ServerAPIVersions.instance.reset!
+ expect(AuthFactoryClass).to receive(:best_request_version).and_call_original
+ expect(class_instance.handle_request(method, url, headers, data)[2]).
+ to include({ "X-Ops-Server-API-Version" => "2" })
+ end
+ end
+
context "when api_version is set to something other than the default" do
let(:class_instance) { Chef::HTTP::Authenticator.new({ :api_version => "-10" }) }
diff --git a/spec/unit/mixin/versioned_api_spec.rb b/spec/unit/mixin/versioned_api_spec.rb
index 4f2418ca24..e8b65158c4 100644
--- a/spec/unit/mixin/versioned_api_spec.rb
+++ b/spec/unit/mixin/versioned_api_spec.rb
@@ -87,6 +87,27 @@ describe Chef::Mixin::VersionedAPIFactory do
end
end
+ describe "#best_request_version" do
+ it "returns a String" do
+ factory_class.add_versioned_api_class V2Class
+ expect(factory_class.best_request_version).to be_a(String)
+ end
+
+ it "returns the most relevant version" do
+ factory_class.add_versioned_api_class V2Class
+ factory_class.add_versioned_api_class V3Class
+ expect(factory_class.best_request_version).to eq("3")
+ end
+ end
+
+ describe "#possible_requests" do
+ it "returns the number of registered classes" do
+ factory_class.add_versioned_api_class V2Class
+ factory_class.add_versioned_api_class V3Class
+ expect(factory_class.possible_requests).to eq(2)
+ end
+ end
+
describe "#new" do
it "creates an instance of the versioned class" do
factory_class.add_versioned_api_class V2Class