Skip to content
This repository was archived by the owner on Jul 1, 2022. It is now read-only.

Commit d93cbde

Browse files
authored
Merge pull request #12 from mitre/csv_attestation
2 parents a439344 + 616cf32 commit d93cbde

File tree

6 files changed

+132
-57
lines changed

6 files changed

+132
-57
lines changed

inspec-reporter-json-hdf.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
1818
spec.required_ruby_version = '~> 2.5'
1919

2020
spec.add_runtime_dependency 'git-lite-version-bump', '~> 0.17', '>= 0.17.3'
21+
spec.add_runtime_dependency 'roo', '~> 2.8.3'
22+
2123
spec.add_development_dependency 'bundler'
2224
spec.add_development_dependency 'bundler-audit'
2325
spec.add_development_dependency 'codeclimate-test-reporter'

lib/inspec-reporter-json-hdf/reporter.rb

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11

22
require 'inspec/plugin/v2'
33
require 'json'
4+
require 'roo'
45

56
VALID_FREQUENCY = %w[annually semiannually quarterly monthly every2weeks weekly every3days daily].freeze
67

78
VALID_STATUSES = %w[passed failed].freeze
89

910
DATE_FORMAT = '%Y-%m-%d'.freeze
1011

12+
SUPPORTED_INCLUDE_TYPES = %w[csv xlsx].freeze
13+
1114
module InspecPlugins::HdfReporter
1215
# Reporter Plugin Class
1316
class Reporter < Inspec.plugin(2, :reporter)
@@ -41,40 +44,24 @@ def report
4144
private
4245

4346
def apply_attestation(results, attestation)
44-
if results.empty?
45-
results = [{
46-
"code_desc": 'Manually verified Status provided through attestation',
47-
"run_time": 0.0,
48-
"start_time": DateTime.now.to_s,
49-
"status": attestation['status'],
50-
"message": attestation_message(attestation)
51-
}]
52-
else
53-
results.each do |result|
54-
result[:message] = 'Automated test returned as passed.' if result[:status].eql?('passed')
55-
result[:message] = result[:skip_message] if result[:status].eql?('skipped')
56-
57-
result[:status] = attestation['status']
58-
result[:message] = result[:message] + attestation_message(attestation)
59-
60-
if result[:backtrace]
61-
result[:message] = result[:message] + "\nbacktrace: #{result[:backtrace]}"
62-
result[:backtrace] = nil
63-
end
64-
end
65-
end
66-
results
47+
results << {
48+
"code_desc": 'Manually verified Status provided through attestation',
49+
"run_time": 0.0,
50+
"start_time": DateTime.now.to_s,
51+
"status": attestation['status'],
52+
"message": attestation_message(attestation)
53+
}
6754
end
6855

6956
def attestation_message(attestation)
70-
"
71-
Attestation:
72-
Status: #{attestation['status']}
73-
Explanation: #{attestation['explanation']}
74-
Updated: #{attestation['updated']}
75-
Updated By: #{attestation['updated_by']}
76-
Frequency: #{attestation['frequency']}
77-
"
57+
[
58+
'Attestation:',
59+
"Status: #{attestation['status']}",
60+
"Explanation: #{attestation['explanation']}",
61+
"Updated: #{attestation['updated']}",
62+
"Updated By: #{attestation['updated_by']}",
63+
"Frequency: #{attestation['frequency']}",
64+
].join("\n")
7865
end
7966

8067
def attestation_expired?(date, frequency)
@@ -88,9 +75,9 @@ def advanced_date(date, frequency)
8875
when 'annually'
8976
parsed_date.next_year(1)
9077
when 'semiannually'
91-
parsed_date.next_year(0.5)
78+
parsed_date.next_month(6)
9279
when 'quarterly'
93-
parsed_date.next_year(0.25)
80+
parsed_date.next_month(3)
9481
when 'monthly'
9582
parsed_date.next_month(1)
9683
when 'every2weeks'
@@ -121,9 +108,47 @@ def valid_status?(status)
121108
status.is_a?(String) && VALID_STATUSES.include?(status.downcase)
122109
end
123110

111+
def parse_include_file(include_file)
112+
if File.exist?(include_file['path'])
113+
if SUPPORTED_INCLUDE_TYPES.include?(include_file['type'])
114+
sheet = Roo::Spreadsheet.open(include_file['path'], extension: include_file['type'].to_sym ).sheet(0)
115+
116+
attestations = sheet.parse(control_id: "Control_ID",
117+
explanation: "Explanation",
118+
frequency: "Frequency",
119+
status: "Status",
120+
updated: "Updated",
121+
updated_by: "Updated_By",
122+
clean:true
123+
)
124+
# Following is required to convert Datetime field returned by xlsx parser to string
125+
attestations.map do |h|
126+
h[:updated] = h[:updated].to_s
127+
end
128+
else
129+
puts "Warning: Invalid `include-attestations-file` type provided. Supported types: #{SUPPORTED_INCLUDE_TYPES.to_s}"
130+
end
131+
else
132+
puts "Warning: Include Attestation File provided '#{include_file['path']}' not found."
133+
end
134+
attestations || []
135+
end
136+
124137
def collect_attestations
125138
plugin_config = Inspec::Config.cached.fetch_plugin_config('inspec-reporter-json-hdf')
126-
attestations = plugin_config['attestations'] || []
139+
attestations = []
140+
141+
# Parse Attestations from include file.
142+
attestations = parse_include_file(plugin_config['include-attestations-file']) if plugin_config['include-attestations-file']
143+
144+
attestations.map!{ |x| x.transform_keys(&:to_s) }
145+
146+
# Merge inline Attestations from config file and `include file` with precedence to inline definitions.
147+
attestations = (plugin_config['attestations'] || []) + attestations
148+
attestations.uniq! {|e| e['control_id'] }
149+
150+
# Remove Attestations records without status provided.
151+
attestations.reject! { |x| x['status'].eql?("") || x['status'].nil? }
127152

128153
if attestations.empty?
129154
puts 'Warning: Attestations not provided; HDF will be generated without attestations.'
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"plugins": {
3+
"inspec-reporter-json-hdf": {
4+
"include-attestations-file": {
5+
"path": "./attestations.xlsx",
6+
"type": "xlsx"
7+
},
8+
"attestations": [
9+
{
10+
"control_id": "test-control-1",
11+
"explanation": "The routine review through interview/examination attests to this control passing",
12+
"frequency": "annually",
13+
"status": "passed",
14+
"updated": "2021-04-06",
15+
"updated_by": "John Doe, ISSO"
16+
},
17+
{
18+
"control_id": "test-control-2",
19+
"explanation": "The routine review through interview/examination attests to this control passing",
20+
"frequency": "semiannually",
21+
"status": "passed",
22+
"updated": "2021-04-06",
23+
"updated_by": "John Doe, ISSO"
24+
}
25+
]
26+
}
27+
},
28+
"version": "1.2"
29+
}
10.6 KB
Binary file not shown.

test/functional/inspec_plugin_functional_test.rb

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,104 +4,111 @@ class InspecPluginFunctionalTest < Minitest::Test
44
def test_with_a_single_pass_non_expired_attestation
55
hdf_json = JSON.parse(File.read('test_hdf.json'))
66
assert_equal('passed', hdf_json['profiles'][0]['controls'][0]['results'][0]['status'])
7-
assert_match(%r(Automated test returned as passed.\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][0]['results'][0]['message'])
7+
assert_equal('passed', hdf_json['profiles'][0]['controls'][0]['results'][1]['status'])
8+
9+
message = hdf_json['profiles'][0]['controls'][0]['results'][1]['message']
10+
assert_match(%r(Attestation:), message)
11+
assert_match(%r(Status: passed), message)
12+
assert_match(%r(Explanation: Non-expired Status passed), message)
13+
assert_match(%r(Updated By: John Doe, ISSO), message)
14+
assert_match(%r(Frequency: annually), message)
815
refute_nil(hdf_json['profiles'][0]['controls'][0]['attestation'])
916
end
1017

11-
1218
def test_with_a_multiple_pass_non_expired_attestation
1319
hdf_json = JSON.parse(File.read('test_hdf.json'))
1420
assert_equal('passed', hdf_json['profiles'][0]['controls'][1]['results'][0]['status'])
1521
assert_equal('passed', hdf_json['profiles'][0]['controls'][1]['results'][1]['status'])
16-
assert_match(%r(Automated test returned as passed.\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][1]['results'][0]['message'])
17-
assert_match(%r(Automated test returned as passed.\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][1]['results'][1]['message'])
22+
assert_equal('passed', hdf_json['profiles'][0]['controls'][1]['results'][2]['status'])
1823
refute_nil(hdf_json['profiles'][0]['controls'][1]['attestation'])
1924
end
2025

2126
def test_with_a_single_fail_non_expired_attestation
2227
hdf_json = JSON.parse(File.read('test_hdf.json'))
23-
assert_equal('passed', hdf_json['profiles'][0]['controls'][2]['results'][0]['status'])
24-
assert_match(%r(\nexpected false\n got true\n\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][2]['results'][0]['message'])
28+
assert_equal('failed', hdf_json['profiles'][0]['controls'][2]['results'][0]['status'])
29+
assert_equal('passed', hdf_json['profiles'][0]['controls'][2]['results'][1]['status'])
2530
refute_nil(hdf_json['profiles'][0]['controls'][2]['attestation'])
2631
end
2732

2833
def test_with_a_multiple_fail_non_expired_attestation
2934
hdf_json = JSON.parse(File.read('test_hdf.json'))
30-
assert_equal('passed', hdf_json['profiles'][0]['controls'][3]['results'][0]['status'])
31-
assert_equal('passed', hdf_json['profiles'][0]['controls'][3]['results'][1]['status'])
32-
assert_match(%r(\nexpected false\n got true\n\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][3]['results'][0]['message'])
33-
assert_match(%r(\nexpected true\n got false\n\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][3]['results'][1]['message'])
35+
assert_equal('failed', hdf_json['profiles'][0]['controls'][3]['results'][0]['status'])
36+
assert_equal('failed', hdf_json['profiles'][0]['controls'][3]['results'][1]['status'])
37+
assert_equal('passed', hdf_json['profiles'][0]['controls'][3]['results'][2]['status'])
3438
refute_nil(hdf_json['profiles'][0]['controls'][3]['attestation'])
3539
end
3640

3741
def test_with_a_single_skip_non_expired_attestation
3842
hdf_json = JSON.parse(File.read('test_hdf.json'))
39-
assert_equal('passed', hdf_json['profiles'][0]['controls'][4]['results'][0]['status'])
40-
assert_match(%r(Manual Test\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][4]['results'][0]['message'])
43+
assert_equal('skipped', hdf_json['profiles'][0]['controls'][4]['results'][0]['status'])
44+
assert_equal('passed', hdf_json['profiles'][0]['controls'][4]['results'][1]['status'])
4145
refute_nil(hdf_json['profiles'][0]['controls'][4]['attestation'])
4246
end
4347
def test_with_a_multiple_skip_non_expired_attestation
4448
hdf_json = JSON.parse(File.read('test_hdf.json'))
45-
assert_equal('passed', hdf_json['profiles'][0]['controls'][5]['results'][0]['status'])
46-
assert_equal('passed', hdf_json['profiles'][0]['controls'][5]['results'][1]['status'])
47-
assert_match(%r(Manual Test2\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][5]['results'][0]['message'])
48-
assert_match(%r(Manual Test2\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][5]['results'][1]['message'])
49+
assert_equal('skipped', hdf_json['profiles'][0]['controls'][5]['results'][0]['status'])
50+
assert_equal('skipped', hdf_json['profiles'][0]['controls'][5]['results'][1]['status'])
51+
assert_equal('passed', hdf_json['profiles'][0]['controls'][5]['results'][2]['status'])
4952
refute_nil(hdf_json['profiles'][0]['controls'][5]['attestation'])
5053
end
5154
def test_with_a_mixed_statuses_non_expired_attestation
5255
hdf_json = JSON.parse(File.read('test_hdf.json'))
53-
assert_equal('passed', hdf_json['profiles'][0]['controls'][6]['results'][0]['status'])
56+
assert_equal('skipped', hdf_json['profiles'][0]['controls'][6]['results'][0]['status'])
5457
assert_equal('passed', hdf_json['profiles'][0]['controls'][6]['results'][1]['status'])
55-
assert_equal('passed', hdf_json['profiles'][0]['controls'][6]['results'][2]['status'])
56-
assert_match(%r(Manual Test\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][6]['results'][0]['message'])
57-
assert_match(%r(Automated test returned as passed.\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][6]['results'][1]['message'])
58-
assert_match(%r(\nexpected false\n got true\n\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][6]['results'][2]['message'])
58+
assert_equal('failed', hdf_json['profiles'][0]['controls'][6]['results'][2]['status'])
59+
assert_equal('passed', hdf_json['profiles'][0]['controls'][6]['results'][3]['status'])
5960
refute_nil(hdf_json['profiles'][0]['controls'][6]['attestation'])
6061
end
6162
def test_with_a_no_statuses_non_expired_attestation
6263
hdf_json = JSON.parse(File.read('test_hdf.json'))
6364
assert_equal('passed', hdf_json['profiles'][0]['controls'][7]['results'][0]['status'])
64-
assert_match(%r(\n Attestation:\n Status: passed\n Explanation: Non-expired Status passed\n), hdf_json['profiles'][0]['controls'][7]['results'][0]['message'])
6565
refute_nil(hdf_json['profiles'][0]['controls'][7]['attestation'])
6666
end
6767
def test_with_a_single_pass_expired_attestation
6868
hdf_json = JSON.parse(File.read('test_hdf.json'))
6969
assert_equal('passed', hdf_json['profiles'][0]['controls'][8]['results'][0]['status'])
70+
assert_nil(hdf_json['profiles'][0]['controls'][8]['results'][1])
7071
refute_nil(hdf_json['profiles'][0]['controls'][8]['attestation'])
7172
end
7273
def test_with_a_multiple_pass_expired_attestation
7374
hdf_json = JSON.parse(File.read('test_hdf.json'))
7475
assert_equal('passed', hdf_json['profiles'][0]['controls'][9]['results'][0]['status'])
7576
assert_equal('passed', hdf_json['profiles'][0]['controls'][9]['results'][1]['status'])
77+
assert_nil(hdf_json['profiles'][0]['controls'][9]['results'][2])
7678
refute_nil(hdf_json['profiles'][0]['controls'][9]['attestation'])
7779
end
7880
def test_with_a_single_fail_expired_attestation
7981
hdf_json = JSON.parse(File.read('test_hdf.json'))
8082
assert_equal('failed', hdf_json['profiles'][0]['controls'][10]['results'][0]['status'])
83+
assert_nil(hdf_json['profiles'][0]['controls'][10]['results'][1])
8184
refute_nil(hdf_json['profiles'][0]['controls'][10]['attestation'])
8285
end
8386
def test_with_a_multiple_fail_expired_attestation
8487
hdf_json = JSON.parse(File.read('test_hdf.json'))
8588
assert_equal('failed', hdf_json['profiles'][0]['controls'][11]['results'][0]['status'])
8689
assert_equal('failed', hdf_json['profiles'][0]['controls'][11]['results'][1]['status'])
90+
assert_nil(hdf_json['profiles'][0]['controls'][11]['results'][2])
8791
refute_nil(hdf_json['profiles'][0]['controls'][11]['attestation'])
8892
end
8993
def test_with_a_single_skip_expired_attestation
9094
hdf_json = JSON.parse(File.read('test_hdf.json'))
9195
assert_equal('skipped', hdf_json['profiles'][0]['controls'][12]['results'][0]['status'])
96+
assert_nil(hdf_json['profiles'][0]['controls'][12]['results'][1])
9297
refute_nil(hdf_json['profiles'][0]['controls'][12]['attestation'])
9398
end
9499
def test_with_a_multiple_skip_expired_attestation
95100
hdf_json = JSON.parse(File.read('test_hdf.json'))
96101
assert_equal('skipped', hdf_json['profiles'][0]['controls'][13]['results'][0]['status'])
97102
assert_equal('skipped', hdf_json['profiles'][0]['controls'][13]['results'][1]['status'])
103+
assert_nil(hdf_json['profiles'][0]['controls'][13]['results'][2])
98104
refute_nil(hdf_json['profiles'][0]['controls'][12]['attestation'])
99105
end
100106
def test_with_a_mixed_statuses_expired_attestation
101107
hdf_json = JSON.parse(File.read('test_hdf.json'))
102108
assert_equal('skipped', hdf_json['profiles'][0]['controls'][14]['results'][0]['status'])
103109
assert_equal('passed', hdf_json['profiles'][0]['controls'][14]['results'][1]['status'])
104110
assert_equal('failed', hdf_json['profiles'][0]['controls'][14]['results'][2]['status'])
111+
assert_nil(hdf_json['profiles'][0]['controls'][14]['results'][3])
105112
refute_nil(hdf_json['profiles'][0]['controls'][14]['attestation'])
106113
end
107114
def test_with_a_no_statuses_expired_attestation

test/generate_attestation_file.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'json'
22
require 'date'
3+
require 'csv'
34

45

56
DATE_FORMAT = '%Y-%m-%d'.freeze
@@ -144,10 +145,21 @@
144145
config_json = {
145146
'plugins' => {
146147
'inspec-reporter-json-hdf' => {
147-
'attestations' => attestations
148+
'include-attestations-file' => {
149+
'type' => 'csv',
150+
'path' => './attestations.csv'
151+
},
152+
'attestations' => attestations[0..4]
148153
}
149154
},
150155
'version' => '1.2'
151156
}
152157

153158
File.write('attestations.json', config_json.to_json)
159+
160+
CSV.open("attestations.csv", "wb") do |csv|
161+
csv << ["Control_ID","Explanation","Frequency","Status","Updated","Updated_By"]
162+
attestations[5..15].each do |hash|
163+
csv << hash.values
164+
end
165+
end

0 commit comments

Comments
 (0)