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