From 98c2f1f66d96215b961e2bcc2093c86e7ae9c072 Mon Sep 17 00:00:00 2001 From: "Jeremy J. Miller" Date: Sat, 11 Jun 2016 22:18:04 -0400 Subject: [PATCH 1/3] adding fixes for refresh_token --- README.md | 4 ++-- libraries/helper.rb | 10 ++++++---- libraries/profile.rb | 5 +++-- libraries/report.rb | 3 ++- recipes/default.rb | 16 +++++++++++----- spec/unit/recipes/default_spec.rb | 25 +++++++++++++++++++++++++ 6 files changed, 49 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8fb75f75..09a9f4ec 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ If you want the audit cookbook to directly report to Chef Compliance, set the `c ```ruby audit: { collector: 'chef-compliance', - server: 'https://compliance-fqdn/api/', + server: 'https://compliance-fqdn/api', token: 'eyJ........................YQ', profiles: { 'base/windows' => true, @@ -164,7 +164,7 @@ It is also possible to use a `refresh_token` instead of an access token: ```ruby audit: { collector: 'chef-compliance', - server: 'https://compliance-fqdn/api/', + server: 'https://compliance-fqdn/api', refresh_token: '5/4T...g==', profiles: { 'base/windows' => true, diff --git a/libraries/helper.rb b/libraries/helper.rb index 619e5e15..d95cb3d1 100644 --- a/libraries/helper.rb +++ b/libraries/helper.rb @@ -13,7 +13,9 @@ def base_chef_server_url end def construct_url(server, path) - path.sub!(%r{^/}, '') # sanitize input + # sanitize inputs + server << '/' unless server =~ %r{/\z} + path.sub!(%r{^/}, '') server = URI(server) server.path = server.path + path if path server @@ -28,7 +30,7 @@ def handle_http_error_code(code) when /404/ Chef::Log.error 'Object does not exist on remote server.' end - msg = 'Could not fetch the profile. Verify the authentication (e.g. token) is set properly' + msg = 'Verify the authentication (e.g. token) is set properly' Chef::Log.error msg fail msg if @raise_if_unreachable end @@ -48,8 +50,8 @@ def with_http_rescue(&block) end # exchanges a refresh token into an access token - def retrieve_access_token(server, refresh_token) - _success, _msg, access_token = Compliance::API.post_refresh_token(url, refresh_token, options['insecure']) + def retrieve_access_token(server_url, refresh_token, insecure = true) + _success, _msg, access_token = Compliance::API.post_refresh_token(server_url, refresh_token, insecure) # TODO we return always the access token, without proper error handling return access_token end diff --git a/libraries/profile.rb b/libraries/profile.rb index 0658f806..24bfcc53 100644 --- a/libraries/profile.rb +++ b/libraries/profile.rb @@ -44,6 +44,7 @@ class ComplianceProfile < Chef::Resource # rubocop:disable Metrics/ClassLength # load the compliance api plugin require 'bundles/inspec-compliance/api' + require 'bundles/inspec-compliance/http' check_inspec end @@ -62,7 +63,7 @@ class ComplianceProfile < Chef::Resource # rubocop:disable Metrics/ClassLength # retrieve access token if a refresh token is set access_token = token - access_token = retrieve_access_token unless refresh_token.nil? + access_token = retrieve_access_token(server, refresh_token) unless refresh_token.nil? if access_token # go direct reqpath ="owners/#{o}/compliance/#{p}/tar" @@ -77,7 +78,7 @@ class ComplianceProfile < Chef::Resource # rubocop:disable Metrics/ClassLength } Net::HTTP.start(url.host, url.port, opts) do |http| resp = with_http_rescue do - http.get(url.path, 'Authorization' => "Bearer #{token}") + http.get(url.path, 'Authorization' => "Bearer #{access_token}") end tf.write(resp.body) end diff --git a/libraries/report.rb b/libraries/report.rb index 236d949e..9d6ab8c9 100644 --- a/libraries/report.rb +++ b/libraries/report.rb @@ -38,13 +38,14 @@ class ComplianceReport < Chef::Resource # retrieve access token if a refresh token is set access_token = token - access_token = retrieve_access_token unless refresh_token.nil? + access_token = retrieve_access_token(server, refresh_token) unless refresh_token.nil? raise_if_unreachable = run_context.node.audit.raise_if_unreachable if run_context.node.audit case collector when 'chef-visibility' Collector::ChefVisibility.new(entity_uuid, run_id, blob).send_report when 'chef-compliance' + access_token = retrieve_access_token(server, refresh_token) unless refresh_token.nil? if access_token && server url = construct_url(server, ::File.join('/owners', o, 'inspec')) Collector::ChefCompliance.new(url, blob, token, raise_if_unreachable).send_report diff --git a/recipes/default.rb b/recipes/default.rb index e4777f77..c932cb76 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -17,10 +17,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -# These two attributes should only be set when connecting directly to Chef Compliance, otherwise they should be nil -token = node['audit']['token'] -server = node['audit']['server'] report_collector = node['audit']['collector'] + +# These attributes should only be set when connecting directly to Chef Compliance, otherwise they should be nil +server = node['audit']['server'] +token = node['audit']['token'] +# Alternatively, specify a refresh_token and it will be used to retrieve an access token +refresh_token = node['audit']['refresh_token'] + interval_seconds = 0 # always run this by default, unless interval is defined if !node['audit']['interval'].nil? && node['audit']['interval']['enabled'] interval_seconds = node['audit']['interval']['time'] * 60 # seconds in interval @@ -59,9 +63,10 @@ formatter formatter server server token token + refresh_token refresh_token path path unless path.nil? inspec_version node['audit']['inspec_version'] - quiet node['audit']['quiet'] + quiet node['audit']['quiet'] unless node['audit']['quiet'].nil? only_if { profile_overdue_to_run?(p, interval_seconds) } action [:fetch, :execute] notifies :touch, "file[#{compliance_cache_directory}/#{p}]", :immediately @@ -74,6 +79,7 @@ server server collector report_collector token token - quiet node['audit']['quiet'] + refresh_token refresh_token + quiet node['audit']['quiet'] unless node['audit']['quiet'].nil? action :execute end if node['audit']['profiles'].values.any? diff --git a/spec/unit/recipes/default_spec.rb b/spec/unit/recipes/default_spec.rb index 16ccb897..2668c815 100644 --- a/spec/unit/recipes/default_spec.rb +++ b/spec/unit/recipes/default_spec.rb @@ -31,6 +31,31 @@ end end + context 'When server and refresh_token are specified' do + let(:chef_run) do + ChefSpec::ServerRunner.new do |node| + node.set['audit']['profiles'] = { 'admin/myprofile' => true } + node.set['audit']['server'] = 'https://my.compliance.test/api' + node.set['audit']['refresh_token'] = 'abcdefg' + end.converge(described_recipe) + end + + it 'fetches and executes compliance_profile[myprofile]' do + expect(chef_run).to fetch_compliance_profile('myprofile').with( + server: 'https://my.compliance.test/api', + refresh_token: 'abcdefg', + ) + expect(chef_run).to execute_compliance_profile('myprofile').with( + server: 'https://my.compliance.test/api', + refresh_token: 'abcdefg', + ) + end + + it 'converges successfully' do + expect { chef_run }.to_not raise_error + end + end + context 'When two profiles are specified' do let(:chef_run) do runner = ChefSpec::ServerRunner.new(platform: 'centos', version: '6.5') From dfbd7b4d81e9f569dd4d0a5588fe758ecb7ec558 Mon Sep 17 00:00:00 2001 From: "Jeremy J. Miller" Date: Wed, 15 Jun 2016 09:17:18 -0400 Subject: [PATCH 2/3] making insecure default to false --- README.md | 3 ++- attributes/default.rb | 2 ++ libraries/helper.rb | 2 +- libraries/profile.rb | 3 ++- libraries/report.rb | 4 ++-- recipes/default.rb | 2 ++ spec/unit/recipes/default_spec.rb | 3 +++ 7 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 09a9f4ec..32cd6114 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,8 @@ audit: { } ``` -It is also possible to use a `refresh_token` instead of an access token: +It is also possible to use a `refresh_token` instead of an access token. + * `insecure` - toggles use of https when retrieving access token. default value is `false` ```ruby audit: { diff --git a/attributes/default.rb b/attributes/default.rb index 93f593f8..f5a9bbb0 100644 --- a/attributes/default.rb +++ b/attributes/default.rb @@ -25,6 +25,8 @@ default['audit']['token'] = nil # refresh_token needed for the 'chef-compliance' collector default['audit']['refresh_token'] = nil +# if self-signed certificates are used +default['audit']['insecure'] = nil # owner needed for the 'chef-compliance' and 'chef-server' collectors default['audit']['owner'] = nil default['audit']['quiet'] = nil diff --git a/libraries/helper.rb b/libraries/helper.rb index d95cb3d1..05d4e503 100644 --- a/libraries/helper.rb +++ b/libraries/helper.rb @@ -50,7 +50,7 @@ def with_http_rescue(&block) end # exchanges a refresh token into an access token - def retrieve_access_token(server_url, refresh_token, insecure = true) + def retrieve_access_token(server_url, refresh_token, insecure) _success, _msg, access_token = Compliance::API.post_refresh_token(server_url, refresh_token, insecure) # TODO we return always the access token, without proper error handling return access_token diff --git a/libraries/profile.rb b/libraries/profile.rb index 24bfcc53..f705b406 100644 --- a/libraries/profile.rb +++ b/libraries/profile.rb @@ -18,6 +18,7 @@ class ComplianceProfile < Chef::Resource # rubocop:disable Metrics/ClassLength property :port, Integer property :token, [String, nil] property :refresh_token, [String, nil] + property :insecure, [TrueClass, FalseClass], default: false property :inspec_version, String, default: 'latest' property :formatter, ['json', 'json-min'], default: 'json-min' property :quiet, [TrueClass, FalseClass], default: true @@ -63,7 +64,7 @@ class ComplianceProfile < Chef::Resource # rubocop:disable Metrics/ClassLength # retrieve access token if a refresh token is set access_token = token - access_token = retrieve_access_token(server, refresh_token) unless refresh_token.nil? + access_token = retrieve_access_token(server, refresh_token, insecure) unless refresh_token.nil? if access_token # go direct reqpath ="owners/#{o}/compliance/#{p}/tar" diff --git a/libraries/report.rb b/libraries/report.rb index 9d6ab8c9..eefa7d4a 100644 --- a/libraries/report.rb +++ b/libraries/report.rb @@ -12,6 +12,7 @@ class ComplianceReport < Chef::Resource property :port, Integer property :token, [String, nil] property :refresh_token, [String, nil] + property :insecure, [TrueClass, FalseClass], default: false property :quiet, [TrueClass, FalseClass], default: true property :collector, ['chef-visibility', 'chef-compliance', 'chef-server'], default: 'chef-server' @@ -38,14 +39,13 @@ class ComplianceReport < Chef::Resource # retrieve access token if a refresh token is set access_token = token - access_token = retrieve_access_token(server, refresh_token) unless refresh_token.nil? raise_if_unreachable = run_context.node.audit.raise_if_unreachable if run_context.node.audit case collector when 'chef-visibility' Collector::ChefVisibility.new(entity_uuid, run_id, blob).send_report when 'chef-compliance' - access_token = retrieve_access_token(server, refresh_token) unless refresh_token.nil? + access_token = retrieve_access_token(server, refresh_token, insecure) unless refresh_token.nil? if access_token && server url = construct_url(server, ::File.join('/owners', o, 'inspec')) Collector::ChefCompliance.new(url, blob, token, raise_if_unreachable).send_report diff --git a/recipes/default.rb b/recipes/default.rb index c932cb76..b0b9a942 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -64,6 +64,7 @@ server server token token refresh_token refresh_token + insecure node['audit']['insecure'] unless node['audit']['insecure'].nil? path path unless path.nil? inspec_version node['audit']['inspec_version'] quiet node['audit']['quiet'] unless node['audit']['quiet'].nil? @@ -80,6 +81,7 @@ collector report_collector token token refresh_token refresh_token + insecure node['audit']['insecure'] unless node['audit']['insecure'].nil? quiet node['audit']['quiet'] unless node['audit']['quiet'].nil? action :execute end if node['audit']['profiles'].values.any? diff --git a/spec/unit/recipes/default_spec.rb b/spec/unit/recipes/default_spec.rb index 2668c815..961625d0 100644 --- a/spec/unit/recipes/default_spec.rb +++ b/spec/unit/recipes/default_spec.rb @@ -37,6 +37,7 @@ node.set['audit']['profiles'] = { 'admin/myprofile' => true } node.set['audit']['server'] = 'https://my.compliance.test/api' node.set['audit']['refresh_token'] = 'abcdefg' + node.set['audit']['insecure'] = true end.converge(described_recipe) end @@ -44,10 +45,12 @@ expect(chef_run).to fetch_compliance_profile('myprofile').with( server: 'https://my.compliance.test/api', refresh_token: 'abcdefg', + insecure: true, ) expect(chef_run).to execute_compliance_profile('myprofile').with( server: 'https://my.compliance.test/api', refresh_token: 'abcdefg', + insecure: true, ) end From 9a73206c7f5d9c3b007f6fc50bd7d9d1ba57fff6 Mon Sep 17 00:00:00 2001 From: Christoph Hartmann Date: Mon, 15 Aug 2016 19:00:26 +0200 Subject: [PATCH 3/3] update examples with refresh token --- examples/kitchen/.kitchen.linux.yml | 2 ++ examples/kitchen/.kitchen.win.yml | 2 ++ examples/kitchen/README.md | 8 ++++++++ libraries/helper.rb | 2 +- recipes/default.rb | 2 +- 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/examples/kitchen/.kitchen.linux.yml b/examples/kitchen/.kitchen.linux.yml index 26b40c71..2a6c6a76 100644 --- a/examples/kitchen/.kitchen.linux.yml +++ b/examples/kitchen/.kitchen.linux.yml @@ -24,6 +24,8 @@ suites: collector: 'chef-compliance' server: <%= ENV['COMPLIANCE_API'] %> token: <%= ENV['COMPLIANCE_ACCESSTOKEN'] %> + refresh_token: <%= ENV['COMPLIANCE_REFRESHTOKEN'] %> + insecure: true owner: admin profiles: base/linux: true diff --git a/examples/kitchen/.kitchen.win.yml b/examples/kitchen/.kitchen.win.yml index 3c54ccad..e51f7926 100644 --- a/examples/kitchen/.kitchen.win.yml +++ b/examples/kitchen/.kitchen.win.yml @@ -36,6 +36,8 @@ suites: collector: 'chef-compliance' server: <%= ENV['COMPLIANCE_API'] %> token: <%= ENV['COMPLIANCE_ACCESSTOKEN'] %> + refresh_token: <%= ENV['COMPLIANCE_REFRESHTOKEN'] %> + insecure: true owner: admin profiles: base/windows: true diff --git a/examples/kitchen/README.md b/examples/kitchen/README.md index 9ca50711..d62854db 100644 --- a/examples/kitchen/README.md +++ b/examples/kitchen/README.md @@ -7,6 +7,14 @@ export COMPLIANCE_API='https://compliance.test/api/' export COMPLIANCE_ACCESSTOKEN='eyJh..GTA' ``` +If you want to use the refresh token, use the following settings: + +``` +export COMPLIANCE_API='https://compliance.test/api' +export COMPLIANCE_REFRESHTOKEN='40/YUP...mA=='' +``` + + ## Converge Linux ``` diff --git a/libraries/helper.rb b/libraries/helper.rb index 05d4e503..23bb3fd4 100644 --- a/libraries/helper.rb +++ b/libraries/helper.rb @@ -30,7 +30,7 @@ def handle_http_error_code(code) when /404/ Chef::Log.error 'Object does not exist on remote server.' end - msg = 'Verify the authentication (e.g. token) is set properly' + msg = "Received HTTP error #{code}. Please verify the authentication (e.g. token) is set properly." Chef::Log.error msg fail msg if @raise_if_unreachable end diff --git a/recipes/default.rb b/recipes/default.rb index b0b9a942..d1c1d813 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -28,8 +28,8 @@ interval_seconds = 0 # always run this by default, unless interval is defined if !node['audit']['interval'].nil? && node['audit']['interval']['enabled'] interval_seconds = node['audit']['interval']['time'] * 60 # seconds in interval + Chef::Log.debug "Auditing this machine every #{interval_seconds} seconds " end -Chef::Log.debug "Auditing this machine every #{interval_seconds} seconds " # set the inspec report format based on collector formatter = report_collector == 'chef-visibility' ? 'json' : 'json-min'