Skip to content

Commit

Permalink
Merge pull request #224 from chef-cookbooks/ap/new-audit
Browse files Browse the repository at this point in the history
Reduce report enrichment, bump cookbook to version 4.0.0
  • Loading branch information
chris-rock authored May 12, 2017
2 parents e187290 + ea81471 commit eeb0849
Show file tree
Hide file tree
Showing 10 changed files with 58 additions and 212 deletions.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,13 @@ This method is sending the report using the `data_collector.server_url` and `dat

If you are using a self-signed certificate, please also read [how to add the Chef Automate certificate to the trusted_certs directory](https://docs.chef.io/setup_visibility_chef_automate.html#add-chef-automate-certificate-to-trusted-certs-directory)

Version compatibility matrix:

| Automate version | InSpec version | Audit Cookbook version |
|--------------------|------------------|--------------------------|
| < 0.8.0 | ≤ 1.23.0 | ≤ 3.1.0 |
| ≥ 0.8.0 | ≥ 1.24.0 | ≥ 4.0.0 |

## Profile Upload to Compliance Server

In order to support build cookbook mode, the `compliance_profile` resource has an `upload` action that allows uploading a compressed
Expand Down Expand Up @@ -464,11 +471,11 @@ Please note that all dependencies to the `inspec` gem must also be hosted in thi

Using the `inspec_version` attribute, please use the following `InSpec` version based on your Chef Compliance version:

| Chef Compliance version | InSpec version | Audit Cookbook version |
|----------------------------|----------------------------|----------------------------|
| Less or equal to 1.1.23 | 0.20.1 | 0.7.0 |
| Greater than 1.1.23 | Greater or equal to 0.22.1 | 0.8.0 |
| Greater or equal to 1.6.8 | Greater or equal to 1.2.0 | 1.0.2 |
| Chef Compliance version | InSpec version | Audit Cookbook version |
|----------------------------|-------------------|---------------------------|
| 1.1.23 | = 0.20.1 | = 0.7.0 |
| > 1.1.23 |0.22.1 | = 0.8.0 |
| 1.6.8 |1.2.0 | = 1.0.2 |

You can see all publicly available InSpec versions [here](https://rubygems.org/gems/inspec/versions)

Expand Down
14 changes: 0 additions & 14 deletions files/default/handler/audit_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,6 @@ def call(opts, profiles)
Chef::Log.info "Summary: #{passed_controls} successful, #{failed_controls} failures, #{skipped_controls} skipped in #{time} s"
end

if !r.nil? && 'json' == opts['format']
# ensure controls are never stored or shipped, since this was an accidential
# addition in InSpec and will be remove in our next major release
r.delete(:controls)

# calculate statistics
stats = count_controls(r[:profiles])

time = 0
time = r[:statistics][:duration] unless r[:statistics].nil?

# count controls
Chef::Log.info "Summary #{stats[:total]} controls: #{stats[:passed][:total]} successful, #{stats[:failed][:total]} failures, #{stats[:skipped][:total]} skipped in #{time} s"
end
Chef::Log.debug "Audit Report #{r}"
r
else
Expand Down
84 changes: 0 additions & 84 deletions libraries/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,90 +133,6 @@ def get_reporters(audit)
end
handle_reporters(audit['reporter'])
end

# Returns a hash with the counted controls based on their status and criticality
# This working on full-json reports
# total: count for all controls in the report, e.g. 100
# successful: count for all controls that executed successfully, e.g. 40
# skipped: count for all skipped controls, e.g. 10
# failed: count for all failed controls, e.g. 50
# minor, major, critical: split the failed count in 3 buckets based on the criticality,
# e.g. minor: 10, major: 15, critical: 25
def count_controls(profiles)
count = {
total: 0,
passed: {
total: 0,
},
skipped: {
total: 0,
},
failed: {
total: 0,
minor: 0,
major: 0,
critical: 0,
},
}
return count unless profiles.is_a?(Array)

profiles.each do |profile|
next unless profile && profile[:controls].is_a?(Array)
profile[:controls].each do |control|
count[:total] += 1
# ensure all impacts are float
control[:impact] = control[:impact].to_f
case control_status(control[:results])
when 'passed'
count[:passed][:total] += 1
when 'skipped'
count[:skipped][:total] += 1
when 'failed'
count[:failed][:total] += 1
criticality = impact_to_s(control[:impact])
count[:failed][criticality.to_sym] += 1 unless criticality.nil?
end
end
end
count
end

# Returns a complince status string based on the passed/failed/skipped controls
def compliance_status(counts)
return 'unknown' unless counts.is_a?(Hash) &&
counts[:failed].is_a?(Hash) &&
counts[:skipped].is_a?(Hash)
if counts[:failed][:total] > 0
'failed'
elsif counts[:total] == counts[:skipped][:total]
'skipped'
else
'passed'
end
end

# Returns a string with the control criticality based on the impact value
def impact_to_s(impact)
if impact < 0.4
'minor'
elsif impact < 0.7
'major'
else
'critical'
end
end

# A control can have multiple tests. Returns 'passed' unless any
# of the results has a status different than 'passed'
def control_status(results)
return unless results.is_a?(Array)
status = 'passed'
results.each do |result|
return 'failed' if result[:status] == 'failed'
status = 'skipped' if result[:status] == 'skipped'
end
status
end
end

::Chef::Recipe.send(:include, ReportHelpers)
39 changes: 17 additions & 22 deletions libraries/reporters/automate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def initialize(opts)
@entity_uuid = opts[:entity_uuid]
@run_id = opts[:run_id]
@node_name = opts[:node_info][:node]
@environment = opts[:node_info][:environment]
@insecure = opts[:insecure]

if defined?(Chef) &&
Expand Down Expand Up @@ -59,6 +60,7 @@ def send_report(report)

begin
Chef::Log.warn "Report to Chef Automate: #{@url}"
Chef::Log.debug "Audit Report: #{json_report}"
http = Chef::HTTP.new(@url)
http.post(nil, json_report, headers)
return true
Expand Down Expand Up @@ -106,33 +108,26 @@ def typed_attributes(profiles)
# revamp of the Automate expected input.
# ***************************************************************************************

def enriched_report(content)
return nil unless content.is_a?(Hash)
final_report = {}
total_duration = content[:statistics][:duration]
inspec_version = content[:version]
def enriched_report(final_report)
return nil unless final_report.is_a?(Hash)

# strip the report to leave only the profiles
final_report[:profiles] = content[:profiles]

# remove nil profiles if any
# Remove nil profiles if any
final_report[:profiles].select! { |p| p }

# set types for profile attributes
# Set types for profile attributes
final_report[:profiles] = typed_attributes(final_report[:profiles])

# add some additional fields to ease report parsing
final_report[:event_type] = 'inspec'
final_report[:event_action] = 'exec'
final_report[:compliance_summary] = count_controls(final_report[:profiles])
final_report[:compliance_summary][:status] = compliance_status(final_report[:compliance_summary])
final_report[:compliance_summary][:node_name] = @node_name
final_report[:compliance_summary][:end_time] = DateTime.now.iso8601
final_report[:compliance_summary][:duration] = total_duration
final_report[:compliance_summary][:inspec_version] = inspec_version
final_report[:entity_uuid] = @entity_uuid
final_report[:run_id] = @run_id
Chef::Log.info "Compliance Summary #{final_report[:compliance_summary]}"
# Label this content as an inspec_report
final_report[:type] = 'inspec_report'

# Ensure controls are never stored or shipped, since this was an accidential
# addition in InSpec and will be remove in the next inspec major release
final_report.delete(:controls)
final_report[:node_name] = @node_name
final_report[:end_time] = DateTime.now.iso8601
final_report[:node_uuid] = @entity_uuid
final_report[:environment] = @environment
final_report[:report_uuid] = @run_id
final_report
end
end
Expand Down
1 change: 1 addition & 0 deletions libraries/reporters/cs_automate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def initialize(opts)
@run_id = opts[:run_id]
@node_name = opts[:node_info][:node]
@insecure = opts[:insecure]
@environment = opts[:node_info][:environment]
@url = opts[:url]
end

Expand Down
2 changes: 1 addition & 1 deletion metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
license 'Apache-2.0'
description 'Allows for fetching and executing compliance profiles, and reporting its results'
long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
version '3.1.0'
version '4.0.0'

source_url 'https://github.com/chef-cookbooks/audit'
issues_url 'https://github.com/chef-cookbooks/audit/issues'
Expand Down
3 changes: 2 additions & 1 deletion spec/data/mock.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class MockData
def self.node_info
{ node: "chef-client.solo" }
{ node: "chef-client.solo",
environment: 'My Prod Env' }
end

def self.inspec_results
Expand Down
28 changes: 12 additions & 16 deletions spec/unit/libraries/automate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
entity_uuid = 'aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz'
run_id = '3f0536f7-3361-4bca-ae53-b45118dceb5d'
insecure = false
@enriched_report_expected = { "profiles":
@enriched_report_expected = {"version":"1.2.1",
"profiles":
[{"name":"tmp_compliance_profile",
"title":"/tmp Compliance Profile",
"summary":"An Example Compliance Profile",
Expand Down Expand Up @@ -58,20 +59,15 @@
"results":[{"status":"passed", "code_desc":"File /tmp should be owned by \"root\"", "run_time":0.028845, "start_time":"2016-10-19 11:09:43 -0400"}]}],
"groups":[{"title":"/tmp Compliance Profile", "controls":["tmp-1.0", "tmp-1.1"], "id":"controls/tmp.rb"}],
"attributes":[{"name":"syslog_pkg", "options":{"default":"rsyslog", "description":"syslog package...", "type":"string"}}]}],
"event_type":"inspec",
"event_action":"exec",
"compliance_summary":{
"total":2,
"passed":{"total":2},
"skipped":{"total":0},
"failed":{"total":0, "minor":0, "major":0, "critical":0},
"status":"passed",
"node_name":"chef-client.solo",
"end_time":"2016-07-19T19:19:19+01:00",
"duration":0.032332,
"inspec_version":"1.2.1"},
"entity_uuid":"aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz",
"run_id":"3f0536f7-3361-4bca-ae53-b45118dceb5d"}
"other_checks": [],
"statistics":{"duration":0.032332},
"type":"inspec_report",
"node_name":"chef-client.solo",
"end_time":"2016-07-19T19:19:19+01:00",
"node_uuid":"aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz",
"environment":"My Prod Env",
"report_uuid":"3f0536f7-3361-4bca-ae53-b45118dceb5d"
}

opts = {
entity_uuid: entity_uuid,
Expand All @@ -89,7 +85,7 @@
@automate = Reporter::ChefAutomate.new(opts)
end

it 'sends report successfully' do
it 'sends report successfully to ChefAutomate' do
allow(DateTime).to receive(:now).and_return(DateTime.parse('2016-07-19T19:19:19+01:00'))
expect(@automate.send_report(MockData.inspec_results)).to eq(true)
end
Expand Down
30 changes: 13 additions & 17 deletions spec/unit/libraries/cs_automate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
entity_uuid = 'aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz'
run_id = '3f0536f7-3361-4bca-ae53-b45118dceb5d'
insecure = false
@enriched_report_expected = { "profiles":
@enriched_report_expected = { "version": "1.2.1",
"profiles":
[{"name": "tmp_compliance_profile",
"title": "/tmp Compliance Profile",
"summary": "An Example Compliance Profile",
Expand Down Expand Up @@ -44,20 +45,15 @@
"results": [{"status": "passed", "code_desc": "File /tmp should be owned by \"root\"", "run_time": 0.028845, "start_time": "2016-10-19 11:09:43 -0400"}]}],
"groups": [{"title": "/tmp Compliance Profile", "controls": ["tmp-1.0", "tmp-1.1"], "id": "controls/tmp.rb"}],
"attributes": [{"name": "syslog_pkg", "options": {"default": "rsyslog", "description": "syslog package...", "type": "string"}}]}],
"event_type": "inspec",
"event_action": "exec",
"compliance_summary": {
"total": 2,
"passed": {"total": 2},
"skipped": {"total": 0},
"failed": {"total": 0, "minor": 0, "major": 0, "critical": 0},
"status": "passed",
"node_name": "chef-client.solo",
"end_time": "2016-07-19T19:19:19+01:00",
"duration": 0.032332,
"inspec_version": "1.2.1"},
"entity_uuid": "aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz",
"run_id": "3f0536f7-3361-4bca-ae53-b45118dceb5d"}
"other_checks": [],
"statistics":{"duration":0.032332},
"type": "inspec_report",
"node_name": "chef-client.solo",
"end_time": "2016-07-19T19:19:19+01:00",
"node_uuid": "aaaaaaaa-709a-475d-bef5-zzzzzzzzzzzz",
"environment": "My Prod Env",
"report_uuid": "3f0536f7-3361-4bca-ae53-b45118dceb5d"
}

opts = {
entity_uuid: entity_uuid,
Expand All @@ -73,12 +69,12 @@
# set data_collector
stub_request(:post, 'https://chef.server/data_collector').
with(:body => @enriched_report_expected.to_json,
:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'2818', 'Content-Type'=>'application/json', 'Host'=>/.+/, 'User-Agent'=>/.+/, 'X-Chef-Version'=>/.+/, 'X-Ops-Authorization-1'=>/.+/, 'X-Ops-Authorization-2'=>/.+/, 'X-Ops-Authorization-3'=>/.+/, 'X-Ops-Authorization-4' => /.+/, 'X-Ops-Authorization-5'=>/.+/, 'X-Ops-Authorization-6'=>/.+/, 'X-Ops-Content-Hash'=>/.+/, 'X-Ops-Server-Api-Version'=>'1', 'X-Ops-Sign'=>'algorithm=sha1;version=1.1;', 'X-Ops-Timestamp'=>/.+/, 'X-Ops-Userid'=>'spec-node', 'X-Remote-Request-Id'=>/.+/}).
:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Length'=>'2706', 'Content-Type'=>'application/json', 'Host'=>/.+/, 'User-Agent'=>/.+/, 'X-Chef-Version'=>/.+/, 'X-Ops-Authorization-1'=>/.+/, 'X-Ops-Authorization-2'=>/.+/, 'X-Ops-Authorization-3'=>/.+/, 'X-Ops-Authorization-4' => /.+/, 'X-Ops-Authorization-5'=>/.+/, 'X-Ops-Authorization-6'=>/.+/, 'X-Ops-Content-Hash'=>/.+/, 'X-Ops-Server-Api-Version'=>'1', 'X-Ops-Sign'=>'algorithm=sha1;version=1.1;', 'X-Ops-Timestamp'=>/.+/, 'X-Ops-Userid'=>'spec-node', 'X-Remote-Request-Id'=>/.+/}).
to_return(:status => 200, :body => "", :headers => {})
@automate = Reporter::ChefServerAutomate.new(opts)
end

it 'sends report successfully' do
it 'sends report successfully to ChefServerAutomate' do
allow(DateTime).to receive(:now).and_return(DateTime.parse('2016-07-19T19:19:19+01:00'))
expect(@automate.send_report(MockData.inspec_results)).to eq(true)
end
Expand Down
52 changes: 0 additions & 52 deletions spec/unit/libraries/helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,56 +35,4 @@
expect(File).to exist("#{expected_file_path}")
File.delete("#{expected_file_path}")
end

it 'returns the correct control status' do
expect(@helpers.control_status(nil)).to eq(nil)
expect(@helpers.control_status([{"status":"failed"}])).to eq('failed')
expect(@helpers.control_status([{"status":"passed"}])).to eq('passed')
expect(@helpers.control_status([{"status":"passed"},{"status":"failed"}])).to eq('failed')
expect(@helpers.control_status([{"status":"failed"},{"status":"passed"}])).to eq('failed')
expect(@helpers.control_status([{"status":"passed"},{"status":"skipped"}])).to eq('skipped')
expect(@helpers.control_status([{"status":"skipped"},{"status":"failed"}])).to eq('failed')
end

it 'reports impact criticality correctly' do
expect(@helpers.impact_to_s(0)).to eq('minor')
expect(@helpers.impact_to_s(0.1234)).to eq('minor')
expect(@helpers.impact_to_s(0.4)).to eq('major')
expect(@helpers.impact_to_s(0.69)).to eq('major')
expect(@helpers.impact_to_s(0.7)).to eq('critical')
expect(@helpers.impact_to_s(1.0)).to eq('critical')
end

it 'reports compliance status like a compliance officer' do
passed = {"total":5, "passed":{"total":3}, "skipped":{"total":2}, "failed":{"total":0, "minor":0, "major":0, "critical":0}}
failed = {"total":5, "passed":{"total":1}, "skipped":{"total":1}, "failed":{"total":3, "minor":1, "major":1, "critical":1}}
skipped = {"total":5, "passed":{"total":0}, "skipped":{"total":5}, "failed":{"total":0, "minor":0, "major":0, "critical":0}}
expect(@helpers.compliance_status(nil)).to eq('unknown')
expect(@helpers.compliance_status(failed)).to eq('failed')
expect(@helpers.compliance_status(passed)).to eq('passed')
expect(@helpers.compliance_status(skipped)).to eq('skipped')
end

it 'counts controls like an accountant' do
profil = [{"name":"test-profile",
"controls":
[{"id":"Checking /etc/missing1.rb existance",
"impact":0,
"results":[{"status":"failed"}]},
{"id":"Checking /etc/missing2.rb existance",
"impact":0.5,
"results":[{"status":"failed"}]},
{"id":"Checking /etc/missing3.rb existance",
"impact":0.8,
"results":[{"status":"failed"}]},
{"id":"Checking /etc/passwd existance",
"impact":0.88,
"results":[{"status":"passed"}]},
{"id":"Checking /etc/something existance",
"impact":1.0,
"results":[{"status":"skipped"}]}]
}]
expected_count = {"total":5, "passed":{"total":1}, "skipped":{"total":1}, "failed":{"total":3, "minor":1, "major":1, "critical":1}}
expect(@helpers.count_controls(profil)).to eq(expected_count)
end
end

0 comments on commit eeb0849

Please sign in to comment.