inspec_tools 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eee290c08f68bf8bb7894537a1871e0bdf5144266ee50932ea70af3018d5f94e
4
- data.tar.gz: 42ed4ee43680aaceb6fa12c03bfe7c29dd88a33399787b7053510716182c627e
3
+ metadata.gz: 315e07faccf97b313b9493963ef7b9b80ffa7a1459bfa661527c4e827de89676
4
+ data.tar.gz: e31f0bc2c0006bcb96b9ee46f34c59b8ba71358c2293d7c39874b0fc80b5292b
5
5
  SHA512:
6
- metadata.gz: 43e9b4038ff40f6aee88a16fb63e536b7d3918f15b8a3f9b3fcf87688eb1a167cfd08a7f45cac6fcdd68a2cdd1b5f1ac9ab9a85a2bd42c983b9e963c0b18d96d
7
- data.tar.gz: cd750538af91a05636ad406b80f1adc83bbacfa382143d472bbced9265227f3da245a8a23dd7de0af3636c1650b1c563c6621dc48f524db1550518e7bcc55ae5
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 --template-file <threshold-file> : yaml file with compliance threshold definition
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> : path to InSpec results JSON
191
- -V --verbose, --no-verbose : print verbose an debug output
192
- -f --json-full, --no-json-full : print the summary STDOUT as JSON
193
- -k --json-counts, --no-json_cou : print the reslut status to STDOUT as JSON
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
 
@@ -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']] 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?
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(File.read(options[:inspec_json])).to_summary
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
- if options[:threshold_file].nil? && options[:threshold_inline].nil?
223
- puts 'Please provide threshold as a yaml file or inline yaml'
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
@@ -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
- def initialize(inspec_json)
21
- @json = JSON.parse(inspec_json)
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 to_summary
25
- @data = Utils::InspecUtil.parse_data_for_ckl(@json)
26
- @summary = {}
27
- @data.keys.each do |control_id|
28
- current_control = @data[control_id]
29
- current_control[:compliance_status] = Utils::InspecUtil.control_status(current_control, true)
30
- current_control[:finding_details] = Utils::InspecUtil.control_finding_details(current_control, current_control[:compliance_status])
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
- compute_summary
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 threshold(threshold = nil)
37
- @summary = to_summary
38
- @threshold = Utils::InspecUtil.to_dotted_hash(YAML.load_file(THRESHOLD_TEMPLATE))
39
- parse_threshold(Utils::InspecUtil.to_dotted_hash(threshold))
40
- threshold_compliance
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
- @summary[:buckets] = {}
47
- @summary[:buckets][:failed] = select_by_status(@data, 'Open')
48
- @summary[:buckets][:passed] = select_by_status(@data, 'NotAFinding')
49
- @summary[:buckets][:no_impact] = select_by_status(@data, 'Not_Applicable')
50
- @summary[:buckets][:skipped] = select_by_status(@data, 'Not_Reviewed')
51
- @summary[:buckets][:error] = select_by_status(@data, 'Profile_Error')
52
-
53
- @summary[:status] = {}
54
- @summary[:status][:failed] = tally_by_impact(@summary[:buckets][:failed])
55
- @summary[:status][:passed] = tally_by_impact(@summary[:buckets][:passed])
56
- @summary[:status][:no_impact] = tally_by_impact(@summary[:buckets][:no_impact])
57
- @summary[:status][:skipped] = tally_by_impact(@summary[:buckets][:skipped])
58
- @summary[:status][:error] = tally_by_impact(@summary[:buckets][:error])
59
-
60
- @summary[:compliance] = compute_compliance
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
- (@summary[:status][:passed][:total]*100.0/
83
- (@summary[:status][:passed][:total]+
84
- @summary[:status][:failed][:total]+
85
- @summary[:status][:skipped][:total]+
86
- @summary[:status][:error][:total])).floor
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.1.0
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-08-11 00:00:00.000000000 Z
14
+ date: 2020-10-16 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: colorize