inspec_tools 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +13 -7
- data/lib/inspec_tools/csv.rb +16 -6
- data/lib/inspec_tools/plugin_cli.rb +8 -28
- data/lib/inspec_tools/summary.rb +108 -76
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 315e07faccf97b313b9493963ef7b9b80ffa7a1459bfa661527c4e827de89676
|
4
|
+
data.tar.gz: e31f0bc2c0006bcb96b9ee46f34c59b8ba71358c2293d7c39874b0fc80b5292b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50409617c4e6142916f328e5850a5ef3f2d99b9e71671714ce0475708c435242911b6d4455f47fd1f5e50a7778b80e673d4ec74e3ee5d8f66ab5530c42fa8ad9
|
7
|
+
data.tar.gz: e34adf2df0ec1b1bcd5d9224a2621565c5c4efbe79177c2326a3ef8eef6e10922cbc025682c6bade7179add2e59b3b6394419da0cf7c82f1fd8dcfb58efe49f3
|
data/README.md
CHANGED
@@ -100,13 +100,15 @@ If the specified threshold is not met, an error code (1) is returned along with
|
|
100
100
|
|
101
101
|
The compliance score are rounded down to the nearest whole number. For example a score of 77.3 would be displayed as 77.
|
102
102
|
|
103
|
+
Thresholds provided inline (i.e. `-i`) override thresholds provided by files (i.e. `-f`).
|
104
|
+
|
103
105
|
```
|
104
106
|
USAGE: inspec_tools compliance [OPTIONS] -j <inspec-json> -i <threshold-inline>
|
105
107
|
inspec_tools compliance [OPTIONS] -j <inspec-json> -f <threshold-file>
|
106
108
|
FLAGS:
|
107
109
|
-j --inspec-json <inspec-json> : path to InSpec results Json
|
108
110
|
-i --template-inline <threshold-inline> : inline compliance threshold definition
|
109
|
-
-f --
|
111
|
+
-f --threshold-file <threshold-file> : yaml file with compliance threshold definition
|
110
112
|
Examples:
|
111
113
|
|
112
114
|
inspec_tools compliance -j examples/sample_json/rhel-simp.json -i '{compliance.min: 80, failed.critical.max: 0, failed.high.max: 0}'
|
@@ -135,11 +137,11 @@ failed.high.max: 1
|
|
135
137
|
|
136
138
|
#### In-Line Examples
|
137
139
|
```
|
138
|
-
{compliance: {min: 90}, failed: {critical: {max: 0}, high: {max: 0}}}
|
140
|
+
"{compliance: {min: 90}, failed: {critical: {max: 0}, high: {max: 0}}}"
|
139
141
|
```
|
140
142
|
|
141
143
|
```
|
142
|
-
{compliance.min: 81, failed.critical.max: 0, failed.high.max: 0}
|
144
|
+
"{compliance.min: 81, failed.critical.max: 0, failed.high.max: 0}"
|
143
145
|
```
|
144
146
|
|
145
147
|
## summary
|
@@ -185,12 +187,16 @@ Using additional flags will override the normal output and only display the outp
|
|
185
187
|
|
186
188
|
USAGE: inspec_tools summary [OPTIONS] -j <inspec-json>
|
187
189
|
|
190
|
+
Thresholds provided inline (i.e. `-i`) override thresholds provided by files (i.e. `-t`).
|
191
|
+
|
192
|
+
|
188
193
|
```
|
189
194
|
FLAGS:
|
190
|
-
-j --inspec-json <inspec-json>
|
191
|
-
-
|
192
|
-
-
|
193
|
-
-
|
195
|
+
-j --inspec-json <inspec-json> : path to InSpec results JSON
|
196
|
+
-f --json-full, --no-json-full : print the summary STDOUT as JSON
|
197
|
+
-k --json-counts, --no-json-counts : print the result status to STDOUT as JSON
|
198
|
+
-t, --threshold-file=THRESHOLD_FILE] : path to threshold YAML file
|
199
|
+
-i, --threshold-inline=THRESHOLD_INLINE] : string of text representing threshold YAML inline
|
194
200
|
|
195
201
|
Examples:
|
196
202
|
|
data/lib/inspec_tools/csv.rb
CHANGED
@@ -21,12 +21,12 @@ module InspecTools
|
|
21
21
|
@csv.shift if @mapping['skip_csv_header']
|
22
22
|
end
|
23
23
|
|
24
|
-
def to_inspec
|
24
|
+
def to_inspec(control_name_prefix: nil)
|
25
25
|
@controls = []
|
26
26
|
@profile = {}
|
27
27
|
@cci_xml = Utils::CciXml.get_cci_list('U_CCI_List.xml')
|
28
28
|
insert_metadata
|
29
|
-
parse_controls
|
29
|
+
parse_controls(control_name_prefix)
|
30
30
|
@profile['controls'] = @controls
|
31
31
|
@profile['sha256'] = Digest::SHA256.hexdigest(@profile.to_s)
|
32
32
|
@profile
|
@@ -66,12 +66,12 @@ module InspecTools
|
|
66
66
|
cell.split("\n").first
|
67
67
|
end
|
68
68
|
|
69
|
-
def parse_controls
|
69
|
+
def parse_controls(prefix)
|
70
70
|
@csv.each do |row|
|
71
71
|
control = {}
|
72
|
-
control['id'] = row[@mapping['control.id']]
|
73
|
-
control['title'] = row[@mapping['control.title']]
|
74
|
-
control['desc'] = row[@mapping['control.desc']]
|
72
|
+
control['id'] = generate_control_id(prefix, row[@mapping['control.id']]) unless @mapping['control.id'].nil? || row[@mapping['control.id']].nil?
|
73
|
+
control['title'] = row[@mapping['control.title']] unless @mapping['control.title'].nil? || row[@mapping['control.title']].nil?
|
74
|
+
control['desc'] = row[@mapping['control.desc']] unless @mapping['control.desc'].nil? || row[@mapping['control.desc']].nil?
|
75
75
|
control['tags'] = {}
|
76
76
|
cci_number = get_cci_number(row[@mapping['control.tags']['cci']])
|
77
77
|
nist = get_nist_reference(cci_number) unless cci_number.nil?
|
@@ -90,5 +90,15 @@ module InspecTools
|
|
90
90
|
@controls << control
|
91
91
|
end
|
92
92
|
end
|
93
|
+
|
94
|
+
def generate_control_id(prefix, id)
|
95
|
+
return id if prefix.nil?
|
96
|
+
|
97
|
+
"#{prefix}-#{id}"
|
98
|
+
end
|
93
99
|
end
|
94
100
|
end
|
101
|
+
|
102
|
+
# rubocop:enable Metrics/AbcSize
|
103
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
104
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'yaml'
|
2
1
|
require 'json'
|
3
2
|
require 'roo'
|
4
3
|
require_relative '../utilities/inspec_util'
|
@@ -13,7 +12,6 @@ module InspecTools
|
|
13
12
|
autoload :CKL, 'inspec_tools/ckl'
|
14
13
|
autoload :Inspec, 'inspec_tools/inspec'
|
15
14
|
autoload :Summary, 'inspec_tools/summary'
|
16
|
-
autoload :Threshold, 'inspec_tools/threshold'
|
17
15
|
autoload :XLSXTool, 'inspec_tools/xlsx_tool'
|
18
16
|
autoload :GenerateMap, 'inspec_tools/generate_map'
|
19
17
|
end
|
@@ -80,10 +78,11 @@ module InspecPlugins
|
|
80
78
|
option :output, required: false, aliases: '-o', default: 'profile'
|
81
79
|
option :format, required: false, aliases: '-f', enum: %w{ruby hash}, default: 'ruby'
|
82
80
|
option :separate_files, required: false, type: :boolean, default: true, aliases: '-s'
|
81
|
+
option :control_name_prefix, required: false, type: :string, aliases: '-p'
|
83
82
|
def csv2inspec
|
84
83
|
csv = CSV.read(options[:csv], encoding: 'ISO8859-1')
|
85
84
|
mapping = YAML.load_file(options[:mapping])
|
86
|
-
profile = InspecTools::CSVTool.new(csv, mapping, options[:csv].split('/')[-1].split('.')[0], options[:verbose]).to_inspec
|
85
|
+
profile = InspecTools::CSVTool.new(csv, mapping, options[:csv].split('/')[-1].split('.')[0], options[:verbose]).to_inspec(control_name_prefix: options[:control_name_prefix])
|
87
86
|
Utils::InspecUtil.unpack_inspec_json(options[:output], profile, options[:separate_files], options[:format])
|
88
87
|
end
|
89
88
|
|
@@ -189,26 +188,14 @@ module InspecPlugins
|
|
189
188
|
desc 'summary', 'summary parses an inspec results json to create a summary json'
|
190
189
|
long_desc InspecTools::Help.text(:summary)
|
191
190
|
option :inspec_json, required: true, aliases: '-j'
|
192
|
-
option :verbose, type: :boolean, aliases: '-V'
|
193
191
|
option :json_full, type: :boolean, required: false, aliases: '-f'
|
194
192
|
option :json_counts, type: :boolean, required: false, aliases: '-k'
|
193
|
+
option :threshold_file, required: false, aliases: '-t'
|
194
|
+
option :threshold_inline, required: false, aliases: '-i'
|
195
195
|
|
196
196
|
def summary
|
197
|
-
summary = InspecTools::Summary.new(
|
198
|
-
|
199
|
-
unless options.include?('json_full') || options.include?('json_counts')
|
200
|
-
puts "\nOverall compliance: #{summary[:compliance]}%\n\n"
|
201
|
-
summary[:status].keys.each do |category|
|
202
|
-
puts category
|
203
|
-
summary[:status][category].keys.each do |impact|
|
204
|
-
puts "\t#{impact} : #{summary[:status][category][impact]}"
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
json_summary = summary.to_json
|
210
|
-
puts json_summary if options[:json_full]
|
211
|
-
puts summary[:status].to_json if options[:json_counts]
|
197
|
+
summary = InspecTools::Summary.new(options: options)
|
198
|
+
summary.output_summary
|
212
199
|
end
|
213
200
|
|
214
201
|
desc 'compliance', 'compliance parses an inspec results json to check if the compliance level meets a specified threshold'
|
@@ -216,17 +203,10 @@ module InspecPlugins
|
|
216
203
|
option :inspec_json, required: true, aliases: '-j'
|
217
204
|
option :threshold_file, required: false, aliases: '-f'
|
218
205
|
option :threshold_inline, required: false, aliases: '-i'
|
219
|
-
option :verbose, type: :boolean, aliases: '-V'
|
220
206
|
|
221
207
|
def compliance
|
222
|
-
|
223
|
-
|
224
|
-
exit(1)
|
225
|
-
end
|
226
|
-
threshold = YAML.load_file(options[:threshold_file]) unless options[:threshold_file].nil?
|
227
|
-
threshold = YAML.safe_load(options[:threshold_inline]) unless options[:threshold_inline].nil?
|
228
|
-
compliance = InspecTools::Summary.new(File.read(options[:inspec_json])).threshold(threshold)
|
229
|
-
compliance ? exit(0) : exit(1)
|
208
|
+
compliance = InspecTools::Summary.new(options: options)
|
209
|
+
compliance.results_meet_threshold? ? exit(0) : exit(1)
|
230
210
|
end
|
231
211
|
end
|
232
212
|
end
|
data/lib/inspec_tools/summary.rb
CHANGED
@@ -2,8 +2,6 @@ require 'json'
|
|
2
2
|
require 'yaml'
|
3
3
|
require_relative '../utilities/inspec_util'
|
4
4
|
|
5
|
-
# rubocop:disable Metrics/AbcSize
|
6
|
-
|
7
5
|
# Impact Definitions
|
8
6
|
CRITICAL = 0.9
|
9
7
|
HIGH = 0.7
|
@@ -16,48 +14,118 @@ TALLYS = %i(total critical high medium low).freeze
|
|
16
14
|
THRESHOLD_TEMPLATE = File.expand_path('../data/threshold.yaml', File.dirname(__FILE__))
|
17
15
|
|
18
16
|
module InspecTools
|
17
|
+
# rubocop:disable Metrics/ClassLength
|
19
18
|
class Summary
|
20
|
-
|
21
|
-
|
19
|
+
attr_reader :json
|
20
|
+
attr_reader :json_full
|
21
|
+
attr_reader :json_counts
|
22
|
+
attr_reader :threshold_file
|
23
|
+
attr_reader :threshold_inline
|
24
|
+
attr_reader :summary
|
25
|
+
attr_reader :threshold
|
26
|
+
|
27
|
+
def initialize(**options)
|
28
|
+
options = options[:options]
|
29
|
+
@json = JSON.parse(File.read(options[:inspec_json]))
|
30
|
+
@json_full = false || options[:json_full]
|
31
|
+
@json_counts = false || options[:json_counts]
|
32
|
+
@threshold = parse_threshold(options[:threshold_inline], options[:threshold_file])
|
33
|
+
@threshold_provided = options[:threshold_inline] || options[:threshold_file]
|
34
|
+
@summary = compute_summary
|
22
35
|
end
|
23
36
|
|
24
|
-
def
|
25
|
-
@
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
37
|
+
def output_summary
|
38
|
+
unless @json_full || @json_counts
|
39
|
+
puts "\nThreshold compliance: #{@threshold['compliance.min']}%"
|
40
|
+
puts "\nOverall compliance: #{@summary[:compliance]}%\n\n"
|
41
|
+
@summary[:status].keys.each do |category|
|
42
|
+
puts category
|
43
|
+
@summary[:status][category].keys.each do |impact|
|
44
|
+
puts "\t#{impact} : #{@summary[:status][category][impact]}"
|
45
|
+
end
|
46
|
+
end
|
31
47
|
end
|
32
|
-
|
33
|
-
@summary
|
48
|
+
|
49
|
+
puts @summary.to_json if @json_full
|
50
|
+
puts @summary[:status].to_json if @json_counts
|
34
51
|
end
|
35
52
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
53
|
+
def results_meet_threshold?
|
54
|
+
raise 'Please provide threshold as a yaml file or inline yaml' unless @threshold_provided
|
55
|
+
|
56
|
+
compliance = true
|
57
|
+
failure = []
|
58
|
+
failure << check_max_compliance(@threshold['compliance.max'], @summary[:compliance], '', 'compliance')
|
59
|
+
failure << check_min_compliance(@threshold['compliance.min'], @summary[:compliance], '', 'compliance')
|
60
|
+
|
61
|
+
BUCKETS.each do |bucket|
|
62
|
+
TALLYS.each do |tally|
|
63
|
+
failure << check_min_compliance(@threshold["#{bucket}.#{tally}.min"], @summary[:status][bucket][tally], bucket, tally)
|
64
|
+
failure << check_max_compliance(@threshold["#{bucket}.#{tally}.max"], @summary[:status][bucket][tally], bucket, tally)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
failure.reject!(&:nil?)
|
69
|
+
compliance = false if failure.length.positive?
|
70
|
+
output(compliance, failure)
|
71
|
+
compliance
|
41
72
|
end
|
42
73
|
|
43
74
|
private
|
44
75
|
|
76
|
+
def check_min_compliance(min, data, bucket, tally)
|
77
|
+
expected_to_string(bucket, tally, 'min', min, data) if min != -1 and data < min
|
78
|
+
end
|
79
|
+
|
80
|
+
def check_max_compliance(max, data, bucket, tally)
|
81
|
+
expected_to_string(bucket, tally, 'max', max, data) if max != -1 and data > max
|
82
|
+
end
|
83
|
+
|
84
|
+
def output(passed_threshold, what_failed)
|
85
|
+
if passed_threshold
|
86
|
+
puts "Overall compliance threshold of #{@threshold['compliance.min']}\% met. Current compliance at #{@summary[:compliance]}\%"
|
87
|
+
else
|
88
|
+
puts 'Compliance threshold was not met: '
|
89
|
+
puts what_failed.join("\n")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def expected_to_string(bucket, tally, maxmin, value, got)
|
94
|
+
return "Expected #{bucket}.#{tally}.#{maxmin}:#{value} got:#{got}" unless bucket.empty? || bucket.nil?
|
95
|
+
|
96
|
+
"Expected #{tally}.#{maxmin}:#{value}\% got:#{got}\%"
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_threshold(threshold_inline, threshold_file)
|
100
|
+
threshold = Utils::InspecUtil.to_dotted_hash(YAML.load_file(THRESHOLD_TEMPLATE))
|
101
|
+
threshold.merge!(Utils::InspecUtil.to_dotted_hash(YAML.load_file(threshold_file))) if threshold_file
|
102
|
+
threshold.merge!(Utils::InspecUtil.to_dotted_hash(YAML.safe_load(threshold_inline))) if threshold_inline
|
103
|
+
threshold
|
104
|
+
end
|
105
|
+
|
45
106
|
def compute_summary
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
107
|
+
data = Utils::InspecUtil.parse_data_for_ckl(@json)
|
108
|
+
|
109
|
+
data.keys.each do |control_id|
|
110
|
+
current_control = data[control_id]
|
111
|
+
current_control[:compliance_status] = Utils::InspecUtil.control_status(current_control, true)
|
112
|
+
current_control[:finding_details] = Utils::InspecUtil.control_finding_details(current_control, current_control[:compliance_status])
|
113
|
+
end
|
114
|
+
|
115
|
+
summary = {}
|
116
|
+
summary[:buckets] = {}
|
117
|
+
summary[:buckets][:failed] = select_by_status(data, 'Open')
|
118
|
+
summary[:buckets][:passed] = select_by_status(data, 'NotAFinding')
|
119
|
+
summary[:buckets][:no_impact] = select_by_status(data, 'Not_Applicable')
|
120
|
+
summary[:buckets][:skipped] = select_by_status(data, 'Not_Reviewed')
|
121
|
+
summary[:buckets][:error] = select_by_status(data, 'Profile_Error')
|
122
|
+
|
123
|
+
summary[:status] = {}
|
124
|
+
%i(failed passed no_impact skipped error).each do |key|
|
125
|
+
summary[:status][key] = tally_by_impact(summary[:buckets][key])
|
126
|
+
end
|
127
|
+
summary[:compliance] = compute_compliance(summary)
|
128
|
+
summary
|
61
129
|
end
|
62
130
|
|
63
131
|
def select_by_impact(controls, impact)
|
@@ -78,49 +146,13 @@ module InspecTools
|
|
78
146
|
tally
|
79
147
|
end
|
80
148
|
|
81
|
-
def compute_compliance
|
82
|
-
(
|
83
|
-
(
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
def threshold_compliance
|
90
|
-
compliance = true
|
91
|
-
failure = []
|
92
|
-
max = @threshold['compliance.max']
|
93
|
-
min = @threshold['compliance.min']
|
94
|
-
if max != -1 and @summary[:compliance] > max
|
95
|
-
compliance = false
|
96
|
-
failure << "Expected compliance.max:#{max} got:#{@summary[:compliance]}"
|
97
|
-
end
|
98
|
-
if min != -1 and @summary[:compliance] < min
|
99
|
-
compliance = false
|
100
|
-
failure << "Expected compliance.min:#{min} got:#{@summary[:compliance]}"
|
101
|
-
end
|
102
|
-
status = @summary[:status]
|
103
|
-
BUCKETS.each do |bucket|
|
104
|
-
TALLYS.each do |tally|
|
105
|
-
max = @threshold["#{bucket}.#{tally}.max"]
|
106
|
-
min = @threshold["#{bucket}.#{tally}.min"]
|
107
|
-
if max != -1 and status[bucket][tally] > max
|
108
|
-
compliance = false
|
109
|
-
failure << "Expected #{bucket}.#{tally}.max:#{max} got:#{status[bucket][tally]}"
|
110
|
-
end
|
111
|
-
if min != -1 and status[bucket][tally] < min
|
112
|
-
compliance = false
|
113
|
-
failure << "Expected #{bucket}.#{tally}.min:#{min} got:#{status[bucket][tally]}"
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
puts failure.join("\n") unless compliance
|
118
|
-
puts 'Compliance threshold met' if compliance
|
119
|
-
compliance
|
120
|
-
end
|
121
|
-
|
122
|
-
def parse_threshold(new_threshold)
|
123
|
-
@threshold.merge!(new_threshold)
|
149
|
+
def compute_compliance(summary)
|
150
|
+
(summary[:status][:passed][:total]*100.0/
|
151
|
+
(summary[:status][:passed][:total]+
|
152
|
+
summary[:status][:failed][:total]+
|
153
|
+
summary[:status][:skipped][:total]+
|
154
|
+
summary[:status][:error][:total])).floor
|
124
155
|
end
|
125
156
|
end
|
157
|
+
# rubocop:enable Metrics/ClassLength
|
126
158
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inspec_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Thew
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2020-
|
14
|
+
date: 2020-10-16 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: colorize
|