Skip to content

Commit

Permalink
Merge pull request #80 from chef-cookbooks/fix_compliance_direct_comm…
Browse files Browse the repository at this point in the history
…unitcation

Fix compliance direct communitcation
  • Loading branch information
arlimus authored Aug 15, 2016
2 parents b6a3e16 + 9a73206 commit 347550a
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 16 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,21 @@ 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,
},
}
```

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: {
collector: 'chef-compliance',
server: 'https://compliance-fqdn/api/',
server: 'https://compliance-fqdn/api',
refresh_token: '5/4T...g==',
profiles: {
'base/windows' => true,
Expand Down
2 changes: 2 additions & 0 deletions attributes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions examples/kitchen/.kitchen.linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions examples/kitchen/.kitchen.win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions examples/kitchen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
10 changes: 6 additions & 4 deletions libraries/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = "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
Expand All @@ -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)
_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
Expand Down
6 changes: 4 additions & 2 deletions libraries/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -44,6 +45,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
Expand All @@ -62,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 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"
Expand All @@ -77,7 +79,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
Expand Down
3 changes: 2 additions & 1 deletion libraries/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -38,13 +39,13 @@ 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?
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, 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
Expand Down
20 changes: 14 additions & 6 deletions recipes/default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@
# 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
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'
Expand Down Expand Up @@ -59,9 +63,11 @@
formatter formatter
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']
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
Expand All @@ -74,6 +80,8 @@
server server
collector report_collector
token token
quiet node['audit']['quiet']
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?
28 changes: 28 additions & 0 deletions spec/unit/recipes/default_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,34 @@
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'
node.set['audit']['insecure'] = true
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',
insecure: true,
)
expect(chef_run).to execute_compliance_profile('myprofile').with(
server: 'https://my.compliance.test/api',
refresh_token: 'abcdefg',
insecure: true,
)
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')
Expand Down

0 comments on commit 347550a

Please sign in to comment.