abide_dev_utils 0.10.1 → 0.11.2

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