inspec_delta 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []