From a3c993fe18661dff59e10957d7ceb004c11995ba Mon Sep 17 00:00:00 2001 From: Jerry Aldrich Date: Fri, 22 Dec 2017 08:01:18 -0600 Subject: [PATCH] Fix OWCA detection for `compliance login` (#2401) * Add handling for OWCA login via `compliance login` OpsWorks Chef Automate currently returns a 200 for the `/compliance/version` endpoint and redirects to the Chef Manage page. This adds support to `inspec compliance login` to accept this as valid behavior and continue with the login. Signed-off-by: Jerry Aldrich * Add test case for 200 response but no Chef Manage Signed-off-by: Jerry Aldrich * Add debug info and split `determine_server_type` Signed-off-by: Jerry Aldrich * Appease RuboCop Signed-off-by: Jerry Aldrich * Remove forced returns from `determine_server_type` Signed-off-by: Jerry Aldrich * Add `false` code path for non-200/non-401 response Signed-off-by: Jerry Aldrich * Reword debug messages Signed-off-by: Jerry Aldrich --- lib/bundles/inspec-compliance/api.rb | 59 +++++++++++- .../bundles/inspec-compliance/api_test.rb | 89 +++++++++++++++---- 2 files changed, 127 insertions(+), 21 deletions(-) diff --git a/lib/bundles/inspec-compliance/api.rb b/lib/bundles/inspec-compliance/api.rb index 17e79dd6e..d67a05730 100755 --- a/lib/bundles/inspec-compliance/api.rb +++ b/lib/bundles/inspec-compliance/api.rb @@ -251,11 +251,66 @@ module Compliance end def self.determine_server_type(url, insecure) - if Compliance::HTTP.get(url + '/compliance/version', nil, insecure).code == '401' + if target_is_automate_server?(url, insecure) :automate - elsif Compliance::HTTP.get(url + '/api/version', nil, insecure).code == '200' + elsif target_is_compliance_server?(url, insecure) :compliance + else + Inspec::Log.debug('Could not determine server type using known endpoints') + nil end end + + def self.target_is_automate_server?(url, insecure) + automate_endpoint = '/compliance/version' + response = Compliance::HTTP.get(url + automate_endpoint, nil, insecure) + case response.code + when '401' + Inspec::Log.debug( + "Received 401 from #{url}#{automate_endpoint} - " \ + 'assuming target is a Chef Automate instance', + ) + true + when '200' + # Chef Automate currently returns 401 for `/compliance/version` but some + # versions of OpsWorks Chef Automate return 200 and a Chef Manage page + # when unauthenticated requests are received. + if response.body.include?('Are You Looking For the Chef Server?') + Inspec::Log.debug( + "Received 200 from #{url}#{automate_endpoint} - " \ + 'assuming target is an OpsWorks Chef Automate instance', + ) + true + else + Inspec::Log.debug( + "Received 200 from #{url}#{automate_endpoint} " \ + 'but did not receive the Chef Manage page - ' \ + 'assuming target is not a Chef Automate instance', + ) + false + end + else + Inspec::Log.debug( + "Received unexpected status code #{response.code} " \ + "from #{url}#{automate_endpoint} - " \ + 'assuming target is not a Chef Automate instance', + ) + false + end + end + + def self.target_is_compliance_server?(url, insecure) + # All versions of Chef Compliance return 200 for `/api/version` + compliance_endpoint = '/api/version' + + response = Compliance::HTTP.get(url + compliance_endpoint, nil, insecure) + return false unless response.code == '200' + + Inspec::Log.debug( + "Received 200 from #{url}#{compliance_endpoint} - " \ + 'assuming target is a Compliance server', + ) + true + end end end diff --git a/test/unit/bundles/inspec-compliance/api_test.rb b/test/unit/bundles/inspec-compliance/api_test.rb index ff7621e27..5082749c4 100644 --- a/test/unit/bundles/inspec-compliance/api_test.rb +++ b/test/unit/bundles/inspec-compliance/api_test.rb @@ -247,32 +247,83 @@ describe Compliance::API do end describe '.determine_server_type' do - it 'returns `automate` when a 401 is received from `https://automate.example.com/compliance/version`' do - good_response = mock + let(:url) { 'https://someserver.onthe.net/' } + + let(:compliance_endpoint) { '/api/version' } + let(:automate_endpoint) { '/compliance/version' } + let(:headers) { nil } + let(:insecure) { true } + + let(:good_response) { mock } + let(:bad_response) { mock } + + it 'returns `:automate` when a 401 is received from `https://URL/compliance/version`' do good_response.stubs(:code).returns('401') - url = 'https://automate.example.com' - Compliance::HTTP.expects(:get).with(url + '/compliance/version', nil, true).returns(good_response) - Compliance::API.determine_server_type(url, true).must_equal(:automate) + + Compliance::HTTP.expects(:get) + .with(url + automate_endpoint, headers, insecure) + .returns(good_response) + + Compliance::API.determine_server_type(url, insecure).must_equal(:automate) end - it 'returns `compliance` when a 200 is received from `https://compliance.example.com/api/version`' do - good_response = mock + # Chef Automate currently returns 401 for `/compliance/version` but some + # versions of OpsWorks Chef Automate return 200 and a Chef Manage page when + # unauthenticated requests are received. + it 'returns `:automate` when a 200 is received from `https://URL/compliance/version`' do good_response.stubs(:code).returns('200') - bad_response = mock - bad_response.stubs(:code).returns('404') - url = 'https://compliance.example.com' - Compliance::HTTP.expects(:get).with(url + '/compliance/version', nil, true).returns(bad_response) - Compliance::HTTP.expects(:get).with(url + '/api/version', nil, true).returns(good_response) - Compliance::API.determine_server_type(url, true).must_equal(:compliance) + good_response.stubs(:body).returns('Are You Looking For the Chef Server?') + + Compliance::HTTP.expects(:get) + .with(url + automate_endpoint, headers, insecure) + .returns(good_response) + + Compliance::API.determine_server_type(url, insecure).must_equal(:automate) end - it 'returns `nil` if no response returns favorably' do - bad_response = mock + it 'returns `nil` if a 200 is received from `https://URL/compliance/version` but not redirected to Chef Manage' do + bad_response.stubs(:code).returns('200') + bad_response.stubs(:body).returns('No Chef Manage here') + + Compliance::HTTP.expects(:get) + .with(url + automate_endpoint, headers, insecure) + .returns(bad_response) + + mock_compliance_response = mock + mock_compliance_response.stubs(:code).returns('404') + Compliance::HTTP.expects(:get) + .with(url + compliance_endpoint, headers, insecure) + .returns(mock_compliance_response) + + Compliance::API.determine_server_type(url, insecure).must_be_nil + end + + + it 'returns `:compliance` when a 200 is received from `https://URL/api/version`' do + good_response.stubs(:code).returns('200') bad_response.stubs(:code).returns('404') - url = 'https://bad.example.com' - Compliance::HTTP.expects(:get).with(url + '/compliance/version', nil, true).returns(bad_response) - Compliance::HTTP.expects(:get).with(url + '/api/version', nil, true).returns(bad_response) - Compliance::API.determine_server_type(url, true).must_be_nil + + Compliance::HTTP.expects(:get) + .with(url + automate_endpoint, headers, insecure) + .returns(bad_response) + Compliance::HTTP.expects(:get) + .with(url + compliance_endpoint, headers, insecure) + .returns(good_response) + + Compliance::API.determine_server_type(url, insecure).must_equal(:compliance) + end + + it 'returns `nil` if it cannot determine the server type' do + bad_response.stubs(:code).returns('404') + + Compliance::HTTP.expects(:get) + .with(url + automate_endpoint, headers, insecure) + .returns(bad_response) + Compliance::HTTP.expects(:get) + .with(url + compliance_endpoint, headers, insecure) + .returns(bad_response) + + Compliance::API.determine_server_type(url, insecure).must_be_nil end end end