abide_dev_utils 0.10.1 → 0.11.2

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +7 -1
  4. data/Gemfile.lock +25 -19
  5. data/Rakefile +28 -0
  6. data/abide_dev_utils.gemspec +1 -0
  7. data/lib/abide_dev_utils/cem/benchmark.rb +490 -0
  8. data/lib/abide_dev_utils/cem/generate/coverage_report.rb +380 -0
  9. data/lib/abide_dev_utils/cem/generate/reference.rb +319 -0
  10. data/lib/abide_dev_utils/cem/generate.rb +11 -0
  11. data/lib/abide_dev_utils/cem/hiera_data/mapping_data/map_data.rb +110 -0
  12. data/lib/abide_dev_utils/cem/hiera_data/mapping_data/mixins.rb +46 -0
  13. data/lib/abide_dev_utils/cem/hiera_data/mapping_data.rb +146 -0
  14. data/lib/abide_dev_utils/cem/hiera_data/resource_data/control.rb +127 -0
  15. data/lib/abide_dev_utils/cem/hiera_data/resource_data/parameters.rb +90 -0
  16. data/lib/abide_dev_utils/cem/hiera_data/resource_data/resource.rb +102 -0
  17. data/lib/abide_dev_utils/cem/hiera_data/resource_data.rb +310 -0
  18. data/lib/abide_dev_utils/cem/hiera_data.rb +7 -0
  19. data/lib/abide_dev_utils/cem/mapping/mapper.rb +282 -0
  20. data/lib/abide_dev_utils/cem/validate/resource_data.rb +33 -0
  21. data/lib/abide_dev_utils/cem/validate.rb +10 -0
  22. data/lib/abide_dev_utils/cem.rb +1 -0
  23. data/lib/abide_dev_utils/cli/cem.rb +98 -0
  24. data/lib/abide_dev_utils/dot_number_comparable.rb +75 -0
  25. data/lib/abide_dev_utils/errors/cem.rb +32 -0
  26. data/lib/abide_dev_utils/errors/general.rb +8 -2
  27. data/lib/abide_dev_utils/errors/ppt.rb +4 -0
  28. data/lib/abide_dev_utils/errors.rb +6 -0
  29. data/lib/abide_dev_utils/markdown.rb +104 -0
  30. data/lib/abide_dev_utils/ppt/class_utils.rb +1 -1
  31. data/lib/abide_dev_utils/ppt/code_gen/data_types.rb +64 -0
  32. data/lib/abide_dev_utils/ppt/code_gen/generate.rb +15 -0
  33. data/lib/abide_dev_utils/ppt/code_gen/resource.rb +59 -0
  34. data/lib/abide_dev_utils/ppt/code_gen/resource_types/base.rb +93 -0
  35. data/lib/abide_dev_utils/ppt/code_gen/resource_types/class.rb +17 -0
  36. data/lib/abide_dev_utils/ppt/code_gen/resource_types/manifest.rb +16 -0
  37. data/lib/abide_dev_utils/ppt/code_gen/resource_types/parameter.rb +16 -0
  38. data/lib/abide_dev_utils/ppt/code_gen/resource_types/strings.rb +13 -0
  39. data/lib/abide_dev_utils/ppt/code_gen/resource_types.rb +6 -0
  40. data/lib/abide_dev_utils/ppt/code_gen.rb +15 -0
  41. data/lib/abide_dev_utils/ppt/code_introspection.rb +102 -0
  42. data/lib/abide_dev_utils/ppt/facter_utils.rb +140 -0
  43. data/lib/abide_dev_utils/ppt/hiera.rb +300 -0
  44. data/lib/abide_dev_utils/ppt/puppet_module.rb +75 -0
  45. data/lib/abide_dev_utils/ppt.rb +6 -5
  46. data/lib/abide_dev_utils/validate.rb +14 -0
  47. data/lib/abide_dev_utils/version.rb +1 -1
  48. data/lib/abide_dev_utils/xccdf/parser/helpers.rb +146 -0
  49. data/lib/abide_dev_utils/xccdf/parser/objects.rb +87 -144
  50. data/lib/abide_dev_utils/xccdf/parser.rb +5 -0
  51. data/lib/abide_dev_utils/xccdf/utils.rb +89 -0
  52. data/lib/abide_dev_utils/xccdf.rb +3 -0
  53. metadata +50 -3
  54. data/lib/abide_dev_utils/ppt/coverage.rb +0 -86
@@ -0,0 +1,490 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'abide_dev_utils/dot_number_comparable'
5
+ require 'abide_dev_utils/errors'
6
+ require 'abide_dev_utils/ppt'
7
+ require 'abide_dev_utils/cem/mapping/mapper'
8
+
9
+ module AbideDevUtils
10
+ module CEM
11
+ # Represents a resource data resource statement
12
+ class Resource
13
+ attr_reader :title, :type
14
+
15
+ def initialize(title, data, framework, mapper)
16
+ @title = title
17
+ @data = data
18
+ @type = data['type']
19
+ @framework = framework
20
+ @mapper = mapper
21
+ @dependent = []
22
+ end
23
+
24
+ # Returns a representation of the actual manifest backing this resource.
25
+ # This is used to gather information from the Puppet code about this
26
+ # resource.
27
+ # @return [AbideDevUtils::Ppt::CodeIntrospection::Manifest]
28
+ # @return [nil] if the manifest could not be found or could not be parsed
29
+ def manifest
30
+ @manifest ||= load_manifest
31
+ end
32
+
33
+ def manifest?
34
+ !manifest.nil?
35
+ end
36
+
37
+ def file_path
38
+ @file_path ||= AbideDevUtils::Ppt::ClassUtils.path_from_class_name((type == 'class' ? title : type))
39
+ end
40
+
41
+ def controls
42
+ @controls || load_controls
43
+ end
44
+
45
+ def cem_options?
46
+ !cem_options.empty?
47
+ end
48
+
49
+ def cem_options
50
+ @cem_options ||= resource_properties('cem_options')
51
+ end
52
+
53
+ def cem_protected?
54
+ !cem_protected.empty?
55
+ end
56
+
57
+ def cem_protected
58
+ @cem_protected ||= resource_properties('cem_protected')
59
+ end
60
+
61
+ def dependent_controls
62
+ @dependent_controls ||= @dependent.flatten.uniq.filter_map { |x| controls.find { |y| y.id == x } }
63
+ end
64
+
65
+ def to_reference
66
+ "#{type.split('::').map(&:capitalize).join('::')}['#{title}']"
67
+ end
68
+
69
+ private
70
+
71
+ attr_reader :data, :framework, :mapper
72
+
73
+ def load_manifest
74
+ AbideDevUtils::Ppt::CodeIntrospection::Manifest.new(file_path)
75
+ rescue StandardError
76
+ nil
77
+ end
78
+
79
+ def resource_properties(prop_name)
80
+ props = Set.new
81
+ return props unless data.key?(prop_name)
82
+
83
+ data[prop_name].each do |param, param_val|
84
+ props << { name: param,
85
+ type: ruby_class_to_puppet_type(param_val.class.to_s),
86
+ default: param_val }
87
+ end
88
+ props
89
+ end
90
+
91
+ def load_controls
92
+ if data['controls'].respond_to?(:keys)
93
+ load_hash_controls(data['controls'], framework, mapper)
94
+ elsif data['controls'].respond_to?(:each_with_index)
95
+ load_array_controls(data['controls'], framework, mapper)
96
+ else
97
+ raise "Control type is invalid. Type: #{data['controls'].class}"
98
+ end
99
+ end
100
+
101
+ def load_hash_controls(ctrls, framework, mapper)
102
+ ctrls.each_with_object([]) do |(name, data), arr|
103
+ if name == 'dependent'
104
+ @dependent << data
105
+ next
106
+ end
107
+ ctrl = Control.new(name, data, self, framework, mapper)
108
+ arr << ctrl
109
+ rescue AbideDevUtils::Errors::ControlIdFrameworkMismatchError,
110
+ AbideDevUtils::Errors::NoMappingDataForControlError
111
+ next
112
+ end
113
+ end
114
+
115
+ def load_array_controls(ctrls, framework, mapper)
116
+ ctrls.each_with_object([]) do |c, arr|
117
+ if c == 'dependent'
118
+ @dependent << c
119
+ next
120
+ end
121
+ ctrl = Control.new(c, 'no_params', self, framework, mapper)
122
+ arr << ctrl
123
+ rescue AbideDevUtils::Errors::ControlIdFrameworkMismatchError,
124
+ AbideDevUtils::Errors::NoMappingDataForControlError
125
+ next
126
+ end
127
+ end
128
+
129
+ def ruby_class_to_puppet_type(class_name)
130
+ pup_type = class_name.split('::').last.capitalize
131
+ case pup_type
132
+ when %r{(Trueclass|Falseclass)}
133
+ 'Boolean'
134
+ when %r{(String|Pathname)}
135
+ 'String'
136
+ when %r{(Integer|Fixnum)}
137
+ 'Integer'
138
+ when %r{(Float|Double)}
139
+ 'Float'
140
+ when %r{Nilclass}
141
+ 'Optional'
142
+ else
143
+ pup_type
144
+ end
145
+ end
146
+ end
147
+
148
+ # Represents a singular rule in a benchmark
149
+ class Control
150
+ include AbideDevUtils::DotNumberComparable
151
+ attr_reader :id, :params, :resource, :framework, :dependent
152
+
153
+ def initialize(id, params, resource, framework, mapper)
154
+ validate_id_with_framework(id, framework, mapper)
155
+ @id = id
156
+ @params = params
157
+ @resource = resource
158
+ @framework = framework
159
+ @mapper = mapper
160
+ raise AbideDevUtils::Errors::NoMappingDataForControlError, @id unless @mapper.get(id)
161
+ end
162
+
163
+ def params?
164
+ !(params.nil? || params.empty? || params == 'no_params') || (resource.cem_options? || resource.cem_protected?)
165
+ end
166
+
167
+ def resource_properties?
168
+ resource.cem_options? || resource.cem_protected?
169
+ end
170
+
171
+ def param_hashes
172
+ return [no_params] unless params?
173
+
174
+ params.each_with_object([]) do |(param, param_val), ar|
175
+ ar << { name: param,
176
+ type: ruby_class_to_puppet_type(param_val.class.to_s),
177
+ default: param_val }
178
+ end
179
+ end
180
+
181
+ def alternate_ids(level: nil, profile: nil)
182
+ id_map = @mapper.get(id, level: level, profile: profile)
183
+ if display_title_type.to_s == @mapper.map_type(id)
184
+ id_map
185
+ else
186
+ alt_ids = id_map.each_with_object([]) do |mapval, arr|
187
+ arr << if display_title_type.to_s == @mapper.map_type(mapval)
188
+ @mapper.get(mapval, level: level, profile: profile)
189
+ else
190
+ mapval
191
+ end
192
+ end
193
+ alt_ids.flatten.uniq
194
+ end
195
+ end
196
+
197
+ def id_map_type
198
+ @mapper.map_type(id)
199
+ end
200
+
201
+ def display_title
202
+ send(display_title_type) unless display_title_type.nil?
203
+ end
204
+
205
+ def levels
206
+ levels_and_profiles[0]
207
+ end
208
+
209
+ def profiles
210
+ levels_and_profiles[1]
211
+ end
212
+
213
+ def valid_maps?
214
+ valid = AbideDevUtils::CEM::Mapping::FRAMEWORK_TYPES[framework].each_with_object([]) do |mtype, arr|
215
+ arr << if @mapper.map_type(id) == mtype
216
+ id
217
+ else
218
+ @mapper.get(id).find { |x| @mapper.map_type(x) == mtype }
219
+ end
220
+ end
221
+ valid.compact.length == AbideDevUtils::CEM::Mapping::FRAMEWORK_TYPES[framework].length
222
+ end
223
+
224
+ def method_missing(meth, *args, &block)
225
+ meth_s = meth.to_s
226
+ if AbideDevUtils::CEM::Mapping::ALL_TYPES.include?(meth_s)
227
+ @mapper.get(id).find { |x| @mapper.map_type(x) == meth_s }
228
+ else
229
+ super
230
+ end
231
+ end
232
+
233
+ def respond_to_missing?(meth, include_private = false)
234
+ AbideDevUtils::CEM::Mapping::ALL_TYPES.include?(meth.to_s) || super
235
+ end
236
+
237
+ def to_h
238
+ {
239
+ id: id,
240
+ display_title: display_title,
241
+ alternate_ids: alternate_ids,
242
+ levels: levels,
243
+ profiles: profiles,
244
+ params: param_hashes,
245
+ resource: resource.to_stubbed_h,
246
+ }
247
+ end
248
+
249
+ private
250
+
251
+ def display_title_type
252
+ if (!vulnid.nil? && !vulnid.is_a?(String)) || !title.is_a?(String)
253
+ nil
254
+ elsif framework == 'stig' && vulnid
255
+ :vulnid
256
+ else
257
+ :title
258
+ end
259
+ end
260
+
261
+ def validate_id_with_framework(id, framework, mapper)
262
+ mtype = mapper.map_type(id)
263
+ return if AbideDevUtils::CEM::Mapping::FRAMEWORK_TYPES[framework].include?(mtype)
264
+
265
+ raise AbideDevUtils::Errors::ControlIdFrameworkMismatchError, [id, mtype, framework]
266
+ end
267
+
268
+ def map
269
+ @map ||= @mapper.get(id)
270
+ end
271
+
272
+ def levels_and_profiles
273
+ @levels_and_profiles ||= find_levels_and_profiles
274
+ end
275
+
276
+ def find_levels_and_profiles
277
+ lvls = []
278
+ profs = []
279
+ @mapper.levels.each do |lvl|
280
+ @mapper.profiles.each do |prof|
281
+ unless @mapper.get(id, level: lvl, profile: prof).nil?
282
+ lvls << lvl
283
+ profs << prof
284
+ end
285
+ end
286
+ end
287
+ [lvls.flatten.compact.uniq, profs.flatten.compact.uniq]
288
+ end
289
+
290
+ def ruby_class_to_puppet_type(class_name)
291
+ pup_type = class_name.split('::').last.capitalize
292
+ case pup_type
293
+ when %r{(Trueclass|Falseclass)}
294
+ 'Boolean'
295
+ when %r{(String|Pathname)}
296
+ 'String'
297
+ when %r{(Integer|Fixnum)}
298
+ 'Integer'
299
+ when %r{(Float|Double)}
300
+ 'Float'
301
+ when %r{Nilclass}
302
+ 'Optional'
303
+ else
304
+ pup_type
305
+ end
306
+ end
307
+
308
+ def no_params
309
+ { name: 'No parameters', type: nil, default: nil }
310
+ end
311
+ end
312
+
313
+ # Repesents a benchmark based on resource and mapping data
314
+ class Benchmark
315
+ attr_reader :osname, :major_version, :os_facts, :osfamily, :hiera_conf, :module_name, :framework
316
+
317
+ def initialize(osname, major_version, hiera_conf, module_name, framework: 'cis')
318
+ @osname = osname
319
+ @major_version = major_version
320
+ @os_facts = AbideDevUtils::Ppt::FacterUtils.recursive_facts_for_os(@osname, @major_version)
321
+ @osfamily = @os_facts['os']['family']
322
+ @hiera_conf = hiera_conf
323
+ @module_name = module_name
324
+ @framework = framework
325
+ @map_cache = {}
326
+ @rules_in_map = {}
327
+ end
328
+
329
+ # Creates Benchmark objects from a Puppet module
330
+ # @param pupmod [AbideDevUtils::Ppt::PuppetModule] A PuppetModule instance
331
+ # @param skip_errors [Boolean] True skips errors and loads non-erroring benchmarks, false raises the error.
332
+ # @return [Array<AbideDevUtils::CEM::Benchmark>] Array of Benchmark instances
333
+ def self.benchmarks_from_puppet_module(pupmod, ignore_all_errors: false, ignore_framework_mismatch: true)
334
+ frameworks = pupmod.hiera_conf.local_hiera_files(hierarchy_name: 'Mapping Data').each_with_object([]) do |hf, ary|
335
+ parts = hf.path.split(pupmod.hiera_conf.default_datadir)[-1].split('/')
336
+ ary << parts[2] unless ary.include?(parts[2])
337
+ end
338
+ pupmod.supported_os.each_with_object([]) do |supp_os, ary|
339
+ osname, majver = supp_os.split('::')
340
+ if majver.is_a?(Array)
341
+ majver.sort.each do |v|
342
+ frameworks.each do |fw|
343
+ benchmark = Benchmark.new(osname,
344
+ v,
345
+ pupmod.hiera_conf,
346
+ pupmod.name(strip_namespace: true),
347
+ framework: fw)
348
+ benchmark.controls
349
+ ary << benchmark
350
+ rescue AbideDevUtils::Errors::MappingDataFrameworkMismatchError => e
351
+ raise e unless ignore_all_errors || ignore_framework_mismatch
352
+ rescue StandardError => e
353
+ raise e unless ignore_all_errors
354
+ end
355
+ end
356
+ else
357
+ frameworks.each do |fw|
358
+ benchmark = Benchmark.new(osname,
359
+ majver,
360
+ pupmod.hiera_conf,
361
+ pupmod.name(strip_namespace: true),
362
+ framework: fw)
363
+ benchmark.controls
364
+ ary << benchmark
365
+ rescue AbideDevUtils::Errors::MappingDataFrameworkMismatchError => e
366
+ raise e unless ignore_all_errors || ignore_framework_mismatch
367
+ rescue StandardError => e
368
+ raise e unless ignore_all_errors
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ def resources
375
+ @resources ||= resource_data["#{module_name}::resources"].each_with_object([]) do |(rtitle, rdata), arr|
376
+ arr << Resource.new(rtitle, rdata, framework, mapper)
377
+ end
378
+ end
379
+
380
+ def controls
381
+ @controls ||= resources.map(&:controls).flatten.sort
382
+ end
383
+
384
+ def mapper
385
+ @mapper ||= AbideDevUtils::CEM::Mapping::Mapper.new(module_name, framework, load_mapping_data)
386
+ end
387
+
388
+ def map_data
389
+ mapper.map_data
390
+ end
391
+
392
+ def resource_data
393
+ @resource_data ||= load_resource_data
394
+ end
395
+
396
+ def title
397
+ mapper.title
398
+ end
399
+
400
+ def version
401
+ mapper.version
402
+ end
403
+
404
+ def title_key
405
+ @title_key ||= "#{title} #{version}"
406
+ end
407
+
408
+ def add_rule(rule_hash)
409
+ @rules << rule_hash
410
+ end
411
+
412
+ def rules_in_map(mtype, level: nil, profile: nil)
413
+ real_mtype = map_type(mtype)
414
+ cache_key = [real_mtype, level, profile].compact.join('-')
415
+ return @rules_in_map[cache_key] if @rules_in_map.key?(cache_key)
416
+
417
+ all_rim = mapper.each_with_array_like(real_mtype) do |(lvl, profs), arr|
418
+ next if lvl == 'benchmark' || (!level.nil? && lvl != level)
419
+
420
+ profs.each do |prof, maps|
421
+ next if !profile.nil? && prof != profile
422
+
423
+ # CIS and STIG differ in that STIG does not have profiles
424
+ control_ids = maps.respond_to?(:keys) ? maps.keys : prof
425
+ arr << control_ids
426
+ end
427
+ end
428
+ @rules_in_map[cache_key] = all_rim.flatten.uniq
429
+ @rules_in_map[cache_key]
430
+ end
431
+
432
+ def map(control_id, level: nil, profile: nil)
433
+ mapper.get(control_id, level: level, profile: profile)
434
+ end
435
+
436
+ def map_type(control_id)
437
+ mapper.map_type(control_id)
438
+ end
439
+
440
+ private
441
+
442
+ def load_mapping_data
443
+ files = case module_name
444
+ when /_windows$/
445
+ cem_windows_mapping_files
446
+ when /_linux$/
447
+ cem_linux_mapping_files
448
+ else
449
+ raise "Module name '#{module_name}' is not a CEM module"
450
+ end
451
+ validate_mapping_files_framework(files).each_with_object({}) do |f, h|
452
+ h[File.basename(f.path, '.yaml')] = YAML.load_file(f.path)
453
+ end
454
+ end
455
+
456
+ def cem_linux_mapping_files
457
+ facts = [['os.name', osname], ['os.release.major', major_version]]
458
+ mapping_files = hiera_conf.local_hiera_files_with_facts(*facts, hierarchy_name: 'Mapping Data')
459
+ raise AbideDevUtils::Errors::MappingFilesNotFoundError, facts if mapping_files.nil? || mapping_files.empty?
460
+
461
+ mapping_files
462
+ end
463
+
464
+ def cem_windows_mapping_files
465
+ facts = ['os.release.major', major_version]
466
+ mapping_files = hiera_conf.local_hiera_files_with_fact(facts[0], facts[1], hierarchy_name: 'Mapping Data')
467
+ raise AbideDevUtils::Errors::MappingFilesNotFoundError, facts if mapping_files.nil? || mapping_files.empty?
468
+
469
+ mapping_files
470
+ end
471
+
472
+ def validate_mapping_files_framework(files)
473
+ validated_files = files.select { |f| f.path_parts.include?(framework) }
474
+ if validated_files.nil? || validated_files.empty?
475
+ raise AbideDevUtils::Errors::MappingDataFrameworkMismatchError, framework
476
+ end
477
+
478
+ validated_files
479
+ end
480
+
481
+ def load_resource_data
482
+ facts = [['os.family', osfamily], ['os.name', osname], ['os.release.major', major_version]]
483
+ rdata_files = hiera_conf.local_hiera_files_with_facts(*facts, hierarchy_name: 'Resource Data')
484
+ raise AbideDevUtils::Errors::ResourceDataNotFoundError, facts if rdata_files.nil? || rdata_files.empty?
485
+
486
+ YAML.load_file(rdata_files[0].path)
487
+ end
488
+ end
489
+ end
490
+ end