inspec_delta 0.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e32fef4ea8661cbb7510dfa92da7367237712a4d1e6c4ee709855d94359b8ce6
4
+ data.tar.gz: f9dc68c827755e37e74a0da42e90e2235475d4edaabcaf37df2760624dba7a55
5
+ SHA512:
6
+ metadata.gz: 574ada892f56ddbfd1dec143c8bd1438ae45330355c9e84e050f648eb599c3598fe715ca3888ac679bd8c05b21c77bbc80b37059e090ecae605dd47397c2df87
7
+ data.tar.gz: d9b76544b91050d1ae6f4486f7fd79bac205e411f5ff8b39b071edc87b6760613595562c1fc76fa7f028d7d911d4ad52907b69d78f7920b450df0908e6a6b3aa
@@ -0,0 +1,29 @@
1
+ [![Gem Version](https://badge.fury.io/rb/inspec_delta.png)](http://badge.fury.io/rb/inspec_delta)
2
+ [![CD Status](https://github.com/jrperron88/inspec_delta/workflows/CI/badge.svg?branch=ETSOE-2220-TravisCI)](https://github.com/jrperron88/inspec_delta/actions?query=workflow%3ACI+branch%3AETSOE-2220-TravisCI)
3
+
4
+ # Inspec Delta
5
+
6
+ This Gem aims to make the maintenance of Inspec profiles representing security benchmarks less of a burden by providing helpful command line tools.
7
+
8
+ For information on STIGs visit the [DoD Cyber Exchange](https://public.cyber.mil/stigs/).
9
+
10
+ For stig definition files visit the [DoD Cyber Exchange Document Library](https://public.cyber.mil/stigs/downloads/).
11
+
12
+ ## Commands
13
+
14
+ ### inspec_delta help
15
+ - This will list all the commands along with their descriptions.
16
+
17
+ ### inspec_delta profile update
18
+ This command updates an existing Inspec profile for a STIG with the metadata from a new revision to that STIG.
19
+ It reads through the XCCDF XML file from the new revision and updates the existing profile's controls as needed.
20
+
21
+ #### Usage
22
+ > inspec_delta profile update -p /path/to/profile -s /path/to/stig.xml
23
+
24
+ #### Options
25
+ ```ruby
26
+
27
+ -p, --pr, [--profile_path=PROFILE_PATH] # Specified path to the root directory of the existing Inspec profile.
28
+ -s, --st, [--stig_file_path=STIG_PATH] # Specified path to the XCCDF XML file from the new STIG revision we are updating to.
29
+ ```
@@ -0,0 +1,4 @@
1
+ require_relative '../lib/inspec_delta'
2
+
3
+ # Runs when gem is run from command line with arguments and options.
4
+ InspecDelta::Command.start(ARGV)
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'rubocop'
5
+ require 'fileutils'
6
+
7
+ require_relative 'inspec_delta'
8
+ require_relative 'inspec_delta/command'
9
+ require_relative 'inspec_delta/objects'
10
+ require_relative 'inspec_delta/parsers'
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'commands/profile'
4
+
5
+ module InspecDelta
6
+ # Public: Various commands issued for the user to interact with. These will
7
+ # be shown when they run inspec_delta.
8
+ class Command < Thor
9
+ # This will cause the return value of inspec_delta to be non-zero when an execution failure occurs.
10
+ # It is necessary to define this to suprress warning messages from Thor. They plan on making
11
+ # the 2.0 release of Thor remove the need to define this.
12
+ # GitHub Issue: https://github.com/erikhuda/thor/issues/244
13
+ def self.exit_on_failure?
14
+ true
15
+ end
16
+
17
+ # Contains subcommands for working with Inspec Profiles
18
+ desc 'profile', 'Subcommands: update'
19
+ subcommand 'profile', InspecDelta::Commands::Profile
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InspecDelta
4
+ module Commands
5
+ # This class will take care of operations related to profile manipulation
6
+ class Profile < Thor
7
+ desc 'update', 'Update profile from STIG file'
8
+ method_option :profile_path,
9
+ aliases: %w[-p --pr],
10
+ desc: 'The path to the directory that contains the profile to modify.',
11
+ required: true
12
+ method_option :stig_file_path,
13
+ aliases: %w[-s --st],
14
+ desc: 'The path to the stig file to apply to the profile.',
15
+ required: true
16
+ def update
17
+ prof = InspecDelta::Object::Profile.new(options[:profile_path])
18
+ prof.update(options[:stig_file_path])
19
+ prof.format
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'objects/control'
4
+ require_relative 'objects/profile'
@@ -0,0 +1,382 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'facets/string'
4
+ require 'inspec-objects'
5
+ require 'ruby_parser'
6
+ require 'ruby2ruby'
7
+
8
+ module InspecDelta
9
+ ##
10
+ # This module represents the objects used in the InspecDelta module
11
+ module Object
12
+ ##
13
+ # This class represents a modified representation of an Inspec Control
14
+ class Control < Inspec::Object::Control
15
+ MAX_LINE_LENGTH = 120
16
+ WORD_WRAP_INDENT = 4
17
+
18
+ attr_accessor :global_code, :control_code
19
+ attr_reader :control_string
20
+
21
+ ##
22
+ # Creates a new Control based on the Inspec Object Control definition
23
+ def initialize
24
+ super
25
+
26
+ @global_code = []
27
+ @control_code = []
28
+ @control_string = ''
29
+ end
30
+
31
+ # Updates a string representation of a Control with string substitutions
32
+ #
33
+ # @param [Control] other - The Control to be merged in
34
+ #
35
+ # @return [control_string] String updated string with the changes from other
36
+ def apply_updates(other)
37
+ apply_updates_title(other.title)
38
+ apply_updates_desc(other.descriptions[:default])
39
+ apply_updates_impact(other.impact)
40
+ apply_updates_tags(other)
41
+ @control_string
42
+ end
43
+
44
+ # Updates a string representation of a Control's title with string substitutions
45
+ #
46
+ # @param [String] title - The title to be applied to the Control
47
+ def apply_updates_title(title)
48
+ return if title.to_s.empty?
49
+
50
+ wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
51
+
52
+ @control_string.sub!(
53
+ /title\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
54
+ "title %q{#{title}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
55
+ )
56
+ end
57
+
58
+ # Updates a string representation of a Control's description with string substitutions
59
+ #
60
+ # @param [Control] desc - The description to be applied to the Control
61
+ def apply_updates_desc(desc)
62
+ return if desc.to_s.empty?
63
+
64
+ wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
65
+
66
+ @control_string.sub!(
67
+ /desc\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
68
+ "desc %q{#{desc}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
69
+ )
70
+ end
71
+
72
+ # Updates a string representation of a Control's impact with string substitutions
73
+ #
74
+ # @param [Decimal] impact - The impact to be applied to the Control
75
+ def apply_updates_impact(impact)
76
+ return if impact.nil?
77
+
78
+ @control_string.sub!(/impact\s+\d\.\d/, "impact #{impact}")
79
+ end
80
+
81
+ # Updates a string representation of a Control's tags with string substitutions
82
+ #
83
+ # @param [Control] other - The Control to be merged in
84
+ def apply_updates_tags(other)
85
+ other.tags.each do |ot|
86
+ tag = @tags.detect { |t| t.key == ot.key }
87
+ next unless tag
88
+
89
+ if ot.value.instance_of?(String)
90
+ apply_updates_tags_string(ot)
91
+ elsif ot.value.instance_of?(Array)
92
+ apply_updates_tags_array(ot)
93
+ elsif ot.value.instance_of?(FalseClass) || ot.value.instance_of?(TrueClass)
94
+ apply_updates_tags_bool(ot)
95
+ end
96
+ end
97
+ end
98
+
99
+ # Updates a string representation of a Control's tags with string substitutions
100
+ #
101
+ # @param [Tag] ot - The Tag to be merged in
102
+ def apply_updates_tags_string(tag)
103
+ if tag.value.empty?
104
+ @control_string.sub!(
105
+ /tag\s+['"]?#{tag.key}['"]?:\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
106
+ "tag '#{tag.key}': nil\n"
107
+ )
108
+ else
109
+ wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
110
+
111
+ @control_string.sub!(
112
+ /tag\s+['"]?#{tag.key}['"]?:\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
113
+ "tag '#{tag.key}': %q{#{tag.value}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
114
+ )
115
+ end
116
+ end
117
+
118
+ # Updates a string representation of a Control's tags with string substitutions
119
+ #
120
+ # @param [Tag] ot - The Tag to be merged in
121
+ def apply_updates_tags_array(tag)
122
+ wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
123
+
124
+ @control_string.sub!(
125
+ /tag\s+['"]?#{tag.key}['"]?:\s+(((%w\().*?(?<!\\)(\)))|((\[).*?(?<!\\)(\]))|(nil))\n/m,
126
+ "tag '#{tag.key}': #{tag.value}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
127
+ )
128
+ end
129
+
130
+ # Updates a string representation of a Control's tags with string substitutions
131
+ #
132
+ # @param [Tag] ot - The Tag to be merged in
133
+ def apply_updates_tags_bool(tag)
134
+ @control_string.sub!(
135
+ /tag\s+['"]?#{tag.key}['"]?:\s+(true|false|'')\n/,
136
+ "tag '#{tag.key}': #{tag.value}\n"
137
+ )
138
+ end
139
+
140
+ # Hash of tags we want to read from the benchmark
141
+ #
142
+ # @return [hash] benchmark_tags
143
+ def self.benchmark_tags
144
+ {
145
+ 'severity' => :severity,
146
+ 'gtitle' => :gtitle,
147
+ 'satisfies' => :satisfies,
148
+ 'gid' => :gid,
149
+ 'rid' => :rid,
150
+ 'stig_id' => :stig_id,
151
+ 'fix_id' => :fix_id,
152
+ 'cci' => :cci,
153
+ 'false_negatives' => :false_negatives,
154
+ 'false_positives' => :false_positives,
155
+ 'documentable' => :documentable,
156
+ 'mitigations' => :mitigations,
157
+ 'severity_override_guidance' => :severity_override_guidance,
158
+ 'potential_impacts' => :potential_impacts,
159
+ 'third_party_tools' => :third_party_tools,
160
+ 'mitigation_controls' => :mitigation_controls,
161
+ 'responsibility' => :responsibility,
162
+ 'ia_controls' => :ia_controls,
163
+ 'check' => :check,
164
+ 'fix' => :fix
165
+ }
166
+ end
167
+
168
+ # Creates a new Control from a benchmark hash definition
169
+ #
170
+ # @param [Hash] benchmark - Hash representation of a benchmark
171
+ #
172
+ # @return [Control] Control representation of the benchmark
173
+ def self.from_benchmark(benchmark)
174
+ control = new
175
+ control.descriptions[:default] = benchmark[:desc]
176
+ control.id = benchmark[:id]
177
+ control.title = benchmark[:title]
178
+ control.impact = impact(benchmark[:severity])
179
+ benchmark_tags.each do |tag, benchmark_key|
180
+ control.add_tag(Inspec::Object::Tag.new(tag, benchmark[benchmark_key]))
181
+ end
182
+ control
183
+ end
184
+
185
+ # Creates a new Control from a ruby definition
186
+ #
187
+ # @param [String] ruby_control_path - path to the ruby file that contains the control
188
+ #
189
+ # @return [Control] Control parsed from file
190
+ def self.from_ruby(ruby_control_path)
191
+ control = new
192
+ control_file_string = File.read(File.expand_path(ruby_control_path))
193
+ send(:parse_ruby, control, RubyParser.new.parse(control_file_string))
194
+ control.instance_variable_set(:@control_string, control_file_string)
195
+ control
196
+ end
197
+
198
+ # Converts the string severity into a decimal representation
199
+ #
200
+ # @param [String] severity - string representation of the severity
201
+ #
202
+ # @return [decimal] numerical representation of the severity if match is found
203
+ # @return [string] severity if no match is found
204
+ def self.impact(severity)
205
+ {
206
+ 'low' => 0.3,
207
+ 'medium' => 0.5,
208
+ 'high' => 0.7
209
+ }.fetch(severity) { severity }
210
+ end
211
+
212
+ # Merges another controll into self
213
+ #
214
+ # Currently we are only mergings objects from the base Inspec::Objects::Control
215
+ # as that is what will be used for updated STIG merges
216
+ # id is not updated as that should be the unique identifier
217
+ #
218
+ # @param [Control] other - another control to take values from.
219
+ #
220
+ # @return [Control] self - updated with values from other control
221
+ def merge_from(other)
222
+ @title = other.title unless other.title.to_s.empty?
223
+ @descriptions[:default] = other.descriptions[:default] unless other.descriptions[:default].to_s.empty?
224
+ @impact = other.impact unless other.impact.nil?
225
+ other.tags.each do |ot|
226
+ tag = @tags.detect { |t| t.key == ot.key }
227
+ tag ? tag.value = ot.value : @tags.push(ot)
228
+ end
229
+ self
230
+ end
231
+
232
+ # Takes an Sexp that is an S-exp representation of a ruby control, and parse it into a control
233
+ #
234
+ # @param [Control] control - A Control that we want to load the ruby Sexp into
235
+ # @param [Sexp] ruby_object - An Sexp representation of a ruby object
236
+ # <syntax>:
237
+ # s(:iter,
238
+ # s(:call, nil, :control, s(:str, <controlId>)),
239
+ # s(:block,
240
+ # s(:call, nil, :title, s(:str, <title>)),
241
+ # s(:call, nil, :desc, s(:str, <desc>)),
242
+ # s(:call, nil, :impact, s(:lit, <impact>)),
243
+ # [<tag>,<RubyCode>](see <Tag> definition)
244
+ # ))
245
+ #
246
+ # <Tag>:
247
+ # s(:call, nil, :tag,
248
+ # s(:hash,
249
+ # s(:lit, :gtitle),
250
+ # <string> || <array_of_strings> || <comment_hash>))
251
+ # <string>:
252
+ # s(:str, 'str1')
253
+ # <array_of_strings>:
254
+ # s(:array,
255
+ # s(:str, 'str1'),
256
+ # s(:str, 'str2')
257
+ # <comment_hash>
258
+ # s(:hash,
259
+ # s(:lit, :"Regular Expression Usage"),
260
+ # s(:str, "RegEx Definition"))
261
+ #
262
+ # @return [Control] control - A Control that has been filled from the ruby_object
263
+ private_class_method def self.parse_base_control(control, ruby_object)
264
+ case ruby_object[0]
265
+ when :call
266
+ send(:parse_base_control_call, control, ruby_object)
267
+ when :block
268
+ ruby_object.each { |x| send(:parse_base_control, control, x) }
269
+ when :iter
270
+ if ruby_object[1][2] == :control
271
+ control.id = ruby_object[1][3][1]
272
+ send(:parse_base_control, control, ruby_object[3])
273
+ else
274
+ control.control_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone))
275
+ end
276
+ else
277
+ control.control_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone)) if ruby_object.instance_of?(Sexp)
278
+ end
279
+ control
280
+ end
281
+
282
+ # Takes an Sexp that is an S-exp representation of a ruby control with a type of call, and parse it into a control
283
+ #
284
+ # @param [Control] control - A Control that we want to load the ruby Sexp into
285
+ # @param [Sexp] ruby_object - An Sexp representation of a ruby object with a call type
286
+ # <syntax> (multiple examples):
287
+ # s(:call, nil, :control, s(:str, <title>))
288
+ # s(:call, nil, :title, s(:str, <title>))
289
+ # s(:call, nil, :desc, s(:str, <desc>))
290
+ # s(:call, nil, :impact, s(:lit, <impact>))
291
+ # <tag> (see <Tag> parameter)
292
+ # <RubyCode>
293
+ #
294
+ # <Tag>:
295
+ # s(:call, nil, :tag,
296
+ # s(:hash,
297
+ # s(:lit, :gtitle),
298
+ # <string> || <array_of_strings> || <comment_hash>))
299
+ # <string>:
300
+ # s(:str, 'str1')
301
+ # <array_of_strings>:
302
+ # s(:array,
303
+ # s(:str, 'str1'),
304
+ # s(:str, 'str2')
305
+ # <comment_hash>
306
+ # s(:hash,
307
+ # s(:lit, :"Regular Expression Usage"),
308
+ # s(:str, "RegEx Definition"))
309
+ #
310
+ # @return [Control] control - A Control that has been filled from the ruby_object
311
+ private_class_method def self.parse_base_control_call(control, ruby_object)
312
+ case ruby_object[2]
313
+ when :control
314
+ control.id = ruby_object[3][1]
315
+ when :title
316
+ control.title = ruby_object[3][1]
317
+ when :desc
318
+ control.descriptions[:default] = ruby_object[3][1]
319
+ when :impact
320
+ control.impact = ruby_object[3][1]
321
+ when :tag
322
+ control.add_tag(
323
+ Inspec::Object::Tag.new(ruby_object[3][1][1].to_s, Ruby2Ruby.new.process(ruby_object[3][2].deep_clone))
324
+ )
325
+ else
326
+ control.control_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone))
327
+ end
328
+ control
329
+ end
330
+
331
+ # Takes an Sexp that is an S-exp representation of a customized ruby control, and parse it into a control
332
+ #
333
+ # @param [Control] control - A Control that we want to load the ruby Sexp into
334
+ # @param [Sexp] ruby_object - An Sexp representation of a ruby object
335
+ # <syntax>:
336
+ # s(:block,
337
+ # s([<RubyCode>[0-9],<Control>,<RubyCode>[0-9]]))
338
+ #
339
+ # @return [Control] control - A Control that has been filled from the ruby_object
340
+ private_class_method def self.parse_ruby(control, ruby_object)
341
+ case ruby_object[0]
342
+ when :call
343
+ control.global_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone))
344
+ when :block
345
+ ruby_object.each { |x| send(:parse_ruby, control, x) }
346
+ when :iter
347
+ case ruby_object[1][2]
348
+ when :control
349
+ send(:parse_base_control, control, ruby_object)
350
+ else
351
+ control.global_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone))
352
+ end
353
+ else
354
+ # Anything that doesn't fall into the other scenarios is pure ruby code that falls outside of the control
355
+ # we want to add this code to the global code to be preserved as code.
356
+ control.global_code.push(Ruby2Ruby.new.process(ruby_object.deep_clone)) if ruby_object.instance_of?(Sexp)
357
+ end
358
+ control
359
+ end
360
+
361
+ # Converts the Control object into a string representation of a Ruby Object
362
+ #
363
+ # unable to use the super function as it is already converted to a string
364
+ #
365
+ # @return [string] the control as a ruby string
366
+ def to_ruby
367
+ res = []
368
+ res.push global_code unless global_code.empty?
369
+ res.push "control #{id.inspect} do"
370
+ res.push " title #{title.inspect}" unless title.to_s.empty?
371
+ res.push " desc #{descriptions[:default].inspect}" unless descriptions[:default].to_s.empty?
372
+ res.push " impact #{impact}" unless impact.nil?
373
+ tags.each do |t|
374
+ res.push(" #{t.to_ruby}")
375
+ end
376
+ res.push control_code unless control_code.empty?
377
+ res.push 'end'
378
+ res.join("\n")
379
+ end
380
+ end
381
+ end
382
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InspecDelta
4
+ ##
5
+ # This module represents the objects used in the InspecDelta module
6
+ module Object
7
+ # This class will take care of operations related to profile manipulation
8
+ class Profile
9
+ # Internal: Initializes the Profile object upon instantiation
10
+ #
11
+ # profile_path: String, path to the inspec profile's root directory.
12
+ # _options: Unused, contains extraneous parameters that may be passed to the initializer
13
+ #
14
+ # Returns: Nothing
15
+ def initialize(profile_path)
16
+ @profile_path = profile_path
17
+ raise StandardError, "Profile directory at #{@profile_path} not found" unless Dir.exist?(@profile_path)
18
+ end
19
+
20
+ # Formats a the ruby controls using rubocop with the rubo config file in the profile
21
+ #
22
+ def format
23
+ control_dir = File.join(File.expand_path(@profile_path), 'controls')
24
+ rubo_file = File.join(File.expand_path(@profile_path), '.rubocop.yml')
25
+ raise StandardError, "Rubocop configuration file at #{rubo_file} not found" unless File.exist?(rubo_file)
26
+
27
+ `rubocop -a #{control_dir} -c #{rubo_file}`
28
+ end
29
+
30
+ # Updates a profile metadata with definitions from a STIG xml file
31
+ #
32
+ # @param [profile_path] String - path to the inspec profile's root directory.
33
+ # @param [stig_file_path] String - The STIG file to be applied to profile.
34
+ def update(stig_file_path)
35
+ raise StandardError, "STIG file at #{stig_file_path} not found" unless File.exist?(stig_file_path)
36
+
37
+ control_dir = "#{@profile_path}/controls"
38
+ benchmark = InspecDelta::Parser::Benchmark.get_benchmark(stig_file_path)
39
+ benchmark.each do |control_id, control|
40
+ benchmark_control = InspecDelta::Object::Control.from_benchmark(control)
41
+ profile_control_path = File.join(File.expand_path(control_dir), "#{control_id}.rb")
42
+ if File.file?(profile_control_path)
43
+ update_existing_control_file(profile_control_path, benchmark_control)
44
+ else
45
+ create_new_control_file(profile_control_path, benchmark_control)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Updates a control file with the updates from the stig
51
+ #
52
+ # @param [profile_control_path] String - The location of the Inspec profile on disk
53
+ # @param [benchmark_control] Control - Control built from the Inspec Benchmark
54
+ def update_existing_control_file(profile_control_path, benchmark_control)
55
+ profile_control = InspecDelta::Object::Control.from_ruby(profile_control_path)
56
+ updated_control = profile_control.apply_updates(benchmark_control)
57
+ File.open(profile_control_path, 'w') { |f| f.puts updated_control }
58
+ end
59
+
60
+ # Creates a control file with the string representation of the benchmark control
61
+ #
62
+ # @param [profile_control_path] String - The location of the Inspec profile on disk
63
+ # @param [benchmark_control] Control - Control built from the Inspec Benchmark
64
+ def create_new_control_file(profile_control_path, benchmark_control)
65
+ File.open(profile_control_path, 'w') { |f| f.puts benchmark_control.to_ruby }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'parsers/benchmark'
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'happy_mapper_tools/stig_attributes'
4
+
5
+ module InspecDelta
6
+ ##
7
+ # This module represents the objects used in the InspecDelta module
8
+ module Parser
9
+ ##
10
+ # This class represents a modified representation of an STIG benchmark
11
+ # which is a collection of Inspec Controls
12
+ class Benchmark
13
+ # Creates a new Benchmark from a STIG file
14
+ #
15
+ # @param [File] file - the STIG xml file
16
+ #
17
+ # @return [Hash] Hash representation of the benchmark - collection of Controls
18
+ def self.get_benchmark(file)
19
+ benchmark = HappyMapperTools::StigAttributes::Benchmark.parse(File.read(file))
20
+ benchmark_title = "#{benchmark.title} :: Version #{benchmark.version}, #{benchmark.plaintext&.plaintext}"
21
+
22
+ mapped_benchmark_group = benchmark.group.map do |b| # rubocop:disable Metrics/BlockLength
23
+ g = {}
24
+
25
+ g[:stig_title] = benchmark_title
26
+
27
+ g[:id] = b.id
28
+ g[:gtitle] = b.title
29
+ g[:description] = b.description
30
+ g[:gid] = b.id
31
+
32
+ rule = b.rule
33
+ g[:rid] = rule.id
34
+ g[:severity] = rule.severity
35
+ g[:stig_id] = rule.version
36
+ g[:title] = rule.title
37
+
38
+ description = rule.description.details
39
+ discussion = description.vuln_discussion
40
+ g[:vuln_discussion] = discussion
41
+ g[:false_negatives] = description.false_negatives
42
+ g[:false_positives] = description.false_positives
43
+ g[:documentable] = description.documentable
44
+ g[:mitigations] = description.mitigations
45
+ g[:severity_override_guidance] = description.severity_override_guidance
46
+ g[:potential_impacts] = description.potential_impacts
47
+ g[:third_party_tools] = description.third_party_tools
48
+ g[:mitigation_controls] = description.mitigation_controls
49
+ g[:responsibility] = description.responsibility
50
+ g[:ia_controls] = description.ia_controls
51
+ g[:desc] = discussion.split('Satisfies: ')[0].strip
52
+ if discussion.split('Satisfies: ').length > 1
53
+ g[:satisfies] = discussion.split('Satisfies: ')[1].split(',').map(&:strip)
54
+ end
55
+
56
+ reference_group = rule.reference
57
+ g[:dc_identifier] = reference_group.dc_identifier
58
+ g[:dc_publisher] = reference_group.dc_publisher
59
+ g[:dc_source] = reference_group.dc_source
60
+ g[:dc_subject] = reference_group.dc_subject
61
+ g[:dc_title] = reference_group.dc_title
62
+ g[:dc_type] = reference_group.dc_type
63
+
64
+ g[:cci] = rule.idents.select { |t| t.start_with?('CCI-') } # XCCDFReader
65
+
66
+ g[:fix] = rule.fixtext
67
+ g[:fix_id] = rule.fix.id
68
+
69
+ g[:check] = rule.check.content
70
+ g[:check_ref_name] = rule.check.content_ref.name
71
+ g[:check_ref] = rule.check.content_ref.href
72
+ [b.id, g]
73
+ end
74
+ mapped_benchmark_group.to_h
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # This module is the overall representation of our InspecDelta implementation
5
+ module InspecDelta
6
+ ##
7
+ # This is the version constant for the inspec_delta gem
8
+ VERSION = '0.1.1'
9
+ end
metadata ADDED
@@ -0,0 +1,198 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inspec_delta
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - JP024221
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-09-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: facets
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.1.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.1.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: inspec-objects
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.1.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.1.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: inspec_tools
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 2.2.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.2.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: ruby2ruby
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 2.4.4
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 2.4.4
125
+ - !ruby/object:Gem::Dependency
126
+ name: ruby_parser
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 3.14.2
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 3.14.2
139
+ - !ruby/object:Gem::Dependency
140
+ name: thor
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 1.0.1
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 1.0.1
153
+ description: Quality of Life tools for managing inspec profiles.
154
+ email:
155
+ - jeremy.perron2@cerner.com
156
+ executables:
157
+ - inspec_delta
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - README.md
162
+ - bin/inspec_delta
163
+ - lib/inspec_delta.rb
164
+ - lib/inspec_delta/command.rb
165
+ - lib/inspec_delta/commands/profile.rb
166
+ - lib/inspec_delta/objects.rb
167
+ - lib/inspec_delta/objects/control.rb
168
+ - lib/inspec_delta/objects/profile.rb
169
+ - lib/inspec_delta/parsers.rb
170
+ - lib/inspec_delta/parsers/benchmark.rb
171
+ - lib/inspec_delta/version.rb
172
+ homepage: https://github.com/cerner/inspec_delta
173
+ licenses:
174
+ - APACHE
175
+ metadata: {}
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - "~>"
183
+ - !ruby/object:Gem::Version
184
+ version: '2.7'
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: 2.7.1
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ requirements: []
194
+ rubygems_version: 3.1.2
195
+ signing_key:
196
+ specification_version: 4
197
+ summary: Quality of Life tools for managing inspec profiles.
198
+ test_files: []