abide_dev_utils 0.11.0 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +18 -31
  3. data/lib/abide_dev_utils/cem/benchmark.rb +335 -136
  4. data/lib/abide_dev_utils/cem/generate/coverage_report.rb +380 -0
  5. data/lib/abide_dev_utils/cem/generate/reference.rb +238 -35
  6. data/lib/abide_dev_utils/cem/generate.rb +5 -4
  7. data/lib/abide_dev_utils/cem/hiera_data/mapping_data/map_data.rb +110 -0
  8. data/lib/abide_dev_utils/cem/hiera_data/mapping_data/mixins.rb +46 -0
  9. data/lib/abide_dev_utils/cem/hiera_data/mapping_data.rb +146 -0
  10. data/lib/abide_dev_utils/cem/hiera_data/resource_data/control.rb +127 -0
  11. data/lib/abide_dev_utils/cem/hiera_data/resource_data/parameters.rb +90 -0
  12. data/lib/abide_dev_utils/cem/hiera_data/resource_data/resource.rb +102 -0
  13. data/lib/abide_dev_utils/cem/hiera_data/resource_data.rb +310 -0
  14. data/lib/abide_dev_utils/cem/hiera_data.rb +7 -0
  15. data/lib/abide_dev_utils/cem/mapping/mapper.rb +161 -34
  16. data/lib/abide_dev_utils/cem/validate/resource_data.rb +33 -0
  17. data/lib/abide_dev_utils/cem/validate.rb +10 -0
  18. data/lib/abide_dev_utils/cem.rb +0 -1
  19. data/lib/abide_dev_utils/cli/cem.rb +20 -2
  20. data/lib/abide_dev_utils/dot_number_comparable.rb +75 -0
  21. data/lib/abide_dev_utils/errors/cem.rb +10 -0
  22. data/lib/abide_dev_utils/ppt/class_utils.rb +1 -1
  23. data/lib/abide_dev_utils/ppt/code_gen/data_types.rb +64 -0
  24. data/lib/abide_dev_utils/ppt/code_gen/generate.rb +15 -0
  25. data/lib/abide_dev_utils/ppt/code_gen/resource.rb +59 -0
  26. data/lib/abide_dev_utils/ppt/code_gen/resource_types/base.rb +93 -0
  27. data/lib/abide_dev_utils/ppt/code_gen/resource_types/class.rb +17 -0
  28. data/lib/abide_dev_utils/ppt/code_gen/resource_types/manifest.rb +16 -0
  29. data/lib/abide_dev_utils/ppt/code_gen/resource_types/parameter.rb +16 -0
  30. data/lib/abide_dev_utils/ppt/code_gen/resource_types/strings.rb +13 -0
  31. data/lib/abide_dev_utils/ppt/code_gen/resource_types.rb +6 -0
  32. data/lib/abide_dev_utils/ppt/code_gen.rb +15 -0
  33. data/lib/abide_dev_utils/ppt/code_introspection.rb +102 -0
  34. data/lib/abide_dev_utils/ppt/hiera.rb +4 -1
  35. data/lib/abide_dev_utils/ppt/puppet_module.rb +2 -1
  36. data/lib/abide_dev_utils/ppt.rb +3 -0
  37. data/lib/abide_dev_utils/version.rb +1 -1
  38. data/lib/abide_dev_utils/xccdf/parser/helpers.rb +146 -0
  39. data/lib/abide_dev_utils/xccdf/parser/objects.rb +87 -144
  40. data/lib/abide_dev_utils/xccdf/parser.rb +5 -0
  41. data/lib/abide_dev_utils/xccdf/utils.rb +89 -0
  42. data/lib/abide_dev_utils/xccdf.rb +193 -63
  43. metadata +27 -3
  44. data/lib/abide_dev_utils/cem/coverage_report.rb +0 -348
@@ -0,0 +1,310 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/errors'
4
+ require 'abide_dev_utils/ppt/facter_utils'
5
+ require 'abide_dev_utils/cem/hiera_data/resource_data/control'
6
+ require 'abide_dev_utils/cem/hiera_data/resource_data/resource'
7
+
8
+ module AbideDevUtils
9
+ module CEM
10
+ module HieraData
11
+ module ResourceData
12
+ # Creates Benchmark objects from a Puppet module
13
+ # @param pupmod [AbideDevUtils::Ppt::PuppetModule] A PuppetModule instance
14
+ # @param skip_errors [Boolean] True skips errors and loads non-erroring benchmarks, false raises the error.
15
+ # @return [Array<AbideDevUtils::CEM::Benchmark>] Array of Benchmark instances
16
+ def self.benchmarks_from_puppet_module(pupmod, ignore_all_errors: false, ignore_framework_mismatch: true)
17
+ frameworks = pupmod.hiera_conf.local_hiera_files(hierarchy_name: 'Mapping Data').each_with_object([]) do |hf, ary|
18
+ parts = hf.path.split(pupmod.hiera_conf.default_datadir)[-1].split('/')
19
+ ary << parts[2] unless ary.include?(parts[2])
20
+ end
21
+ pupmod.supported_os.each_with_object([]) do |supp_os, ary|
22
+ osname, majver = supp_os.split('::')
23
+ if majver.is_a?(Array)
24
+ majver.sort.each do |v|
25
+ frameworks.each do |fw|
26
+ benchmark = Benchmark.new(osname,
27
+ v,
28
+ pupmod.hiera_conf,
29
+ pupmod.name(strip_namespace: true),
30
+ framework: fw)
31
+ benchmark.controls
32
+ ary << benchmark
33
+ rescue AbideDevUtils::Errors::MappingDataFrameworkMismatchError => e
34
+ raise e unless ignore_all_errors || ignore_framework_mismatch
35
+ rescue StandardError => e
36
+ raise e unless ignore_all_errors
37
+ end
38
+ end
39
+ else
40
+ frameworks.each do |fw|
41
+ benchmark = Benchmark.new(osname,
42
+ majver,
43
+ pupmod.hiera_conf,
44
+ pupmod.name(strip_namespace: true),
45
+ framework: fw)
46
+ benchmark.controls
47
+ ary << benchmark
48
+ rescue AbideDevUtils::Errors::MappingDataFrameworkMismatchError => e
49
+ raise e unless ignore_all_errors || ignore_framework_mismatch
50
+ rescue StandardError => e
51
+ raise e unless ignore_all_errors
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # Repesents a benchmark based on resource and mapping data
58
+ class Benchmark
59
+ attr_reader :osname, :major_version, :os_facts, :osfamily, :hiera_conf, :module_name, :framework
60
+
61
+ def initialize(osname, major_version, hiera_conf, module_name, framework: 'cis')
62
+ @osname = osname
63
+ @major_version = major_version
64
+ @os_facts = AbideDevUtils::Ppt::FacterUtils.recursive_facts_for_os(@osname, @major_version)
65
+ @osfamily = @os_facts['os']['family']
66
+ @hiera_conf = hiera_conf
67
+ @module_name = module_name
68
+ @framework = framework
69
+ @map_cache = {}
70
+ @rules_in_map = {}
71
+ end
72
+
73
+ def resources
74
+ @resources ||= resource_data["#{module_name}::resources"].each_with_object([]) do |(rtitle, rdata), arr|
75
+ arr << Resource.new(rtitle, rdata, framework, mapper)
76
+ end
77
+ end
78
+
79
+ def controls
80
+ @controls ||= resources.map(&:controls).flatten.sort
81
+ end
82
+
83
+ def mapper
84
+ @mapper ||= AbideDevUtils::CEM::HieraData::MappingData::Mapper.new(module_name, framework, load_mapping_data)
85
+ end
86
+
87
+ def map_data
88
+ mapper.map_data
89
+ end
90
+
91
+ def resource_data
92
+ @resource_data ||= load_resource_data
93
+ end
94
+
95
+ def title
96
+ mapper.title
97
+ end
98
+
99
+ def version
100
+ mapper.version
101
+ end
102
+
103
+ def title_key
104
+ @title_key ||= "#{title} #{version}"
105
+ end
106
+
107
+ def add_rule(rule_hash)
108
+ @rules << rule_hash
109
+ end
110
+
111
+ def rules_in_map(mtype, level: nil, profile: nil)
112
+ real_mtype = map_type(mtype)
113
+ cache_key = [real_mtype, level, profile].compact.join('-')
114
+ return @rules_in_map[cache_key] if @rules_in_map.key?(cache_key)
115
+
116
+ all_rim = mapper.each_with_array_like(real_mtype) do |(lvl, profs), arr|
117
+ next if lvl == 'benchmark' || (!level.nil? && lvl != level)
118
+
119
+ profs.each do |prof, maps|
120
+ next if !profile.nil? && prof != profile
121
+
122
+ # CIS and STIG differ in that STIG does not have profiles
123
+ control_ids = maps.respond_to?(:keys) ? maps.keys : prof
124
+ arr << control_ids
125
+ end
126
+ end
127
+ @rules_in_map[cache_key] = all_rim.flatten.uniq
128
+ @rules_in_map[cache_key]
129
+ end
130
+
131
+ def map(control_id, level: nil, profile: nil)
132
+ mapper.get(control_id, level: level, profile: profile)
133
+ end
134
+
135
+ def map_type(control_id)
136
+ mapper.map_type(control_id)
137
+ end
138
+
139
+ private
140
+
141
+ # def load_rules
142
+ # @rules ||= resources.map(&:controls).flatten
143
+ # rule_hash = resource_data["#{module_name}::resources"].each_with_object({}) do |(_, rdata), rhsh|
144
+ # unless rdata.key?('controls')
145
+ # puts "Controls key not found in #{rdata}"
146
+ # next
147
+ # end
148
+ # rdata['controls'].each do |control, control_data|
149
+ # rule = Rule.new(control, data, framework, mapper)
150
+ # rhsh[rule.display_title] = {} unless rhsh.key?(rule.display_title)
151
+ # rhsh[rule.display_title]['number'] = rule.number
152
+ # rhsh[rule.display_title]['alternate_ids'] = alternate_ids
153
+ # rhsh[rule.display_title]['params'] = [] unless rhsh[rule.display_title].key?('params')
154
+ # rhsh[rule.display_title]['level'] = [] unless rhsh[rule.display_title].key?('level')
155
+ # rhsh[rule.display_title]['profile'] = [] unless rhsh[rule.display_title].key?('profile')
156
+ # param_hashes(control_data).each do |param_hash|
157
+ # next if rhsh[rule_title]['params'].include?(param_hash[:name])
158
+
159
+ # rhsh[rule_title]['params'] << param_hash
160
+ # end
161
+ # levels, profiles = find_levels_and_profiles(control)
162
+ # unless rhsh[rule_title]['level'] == levels
163
+ # rhsh[rule_title]['level'] = rhsh[rule_title]['level'] || levels
164
+ # end
165
+ # unless rhsh[rule_title]['profile'] == profiles
166
+ # rhsh[rule_title]['profile'] = rhsh[rule_title]['profile'] || profiles
167
+ # end
168
+ # rhsh[rule_title]['resource'] = rdata['type']
169
+ # end
170
+ # end
171
+ # sort_rules(rule_hash)
172
+ # end
173
+
174
+ # def param_hashes(control_data)
175
+ # return [] if control_data.nil? || control_data.empty?
176
+
177
+ # p_hashes = []
178
+ # if !control_data.respond_to?(:each) && control_data == 'no_params'
179
+ # p_hashes << no_params
180
+ # else
181
+ # control_data.each do |param, param_val|
182
+ # p_hashes << {
183
+ # name: param,
184
+ # type: ruby_class_to_puppet_type(param_val.class.to_s),
185
+ # default: param_val,
186
+ # }
187
+ # end
188
+ # end
189
+ # p_hashes
190
+ # end
191
+
192
+ # def no_params
193
+ # { name: 'No parameters', type: nil, default: nil }
194
+ # end
195
+
196
+ # # We sort the rules by their control number so they
197
+ # # appear in the REFERENCE in benchmark order
198
+ # def sort_rules(rule_hash)
199
+ # sorted = rule_hash.dup.sort_by do |_, v|
200
+ # control_num_to_int(v['number'])
201
+ # end
202
+ # sorted.to_h
203
+ # end
204
+
205
+ # In order to sort the rules by their control number,
206
+ # we need to convert the control number to an integer.
207
+ # This is a rough conversion, but should be sufficient
208
+ # for the purposes of sorting. The multipliers are
209
+ # the 20th, 15th, 10th, and 5th numbers in the Fibonacci
210
+ # sequence, then 1 after that. The reason for this is to
211
+ # ensure a "spiraled" wieghting of the sections in the control
212
+ # number, with 1st section having the most sorting weight, 2nd
213
+ # having second most, etc. However, the differences in the multipliers
214
+ # are such that it would be difficult for the product of a lesser-weighted
215
+ # section to be greater than a greater-weighted section.
216
+ # def control_num_to_int(control_num)
217
+ # multipliers = [6765, 610, 55, 5, 1]
218
+ # nsum = 0
219
+ # midx = 0
220
+ # control_num.split('.').each do |num|
221
+ # multiplier = midx >= multipliers.length ? 1 : multipliers[midx]
222
+ # nsum += num.to_i * multiplier
223
+ # midx += 1
224
+ # end
225
+ # nsum
226
+ # end
227
+
228
+ # def find_levels_and_profiles(control_id)
229
+ # levels = []
230
+ # profiles = []
231
+ # mapper.each_like(control_id) do |lvl, profile_hash|
232
+ # next if lvl == 'benchmark'
233
+
234
+ # profile_hash.each do |prof, _|
235
+ # unless map(control_id, level: lvl, profile: prof).nil?
236
+ # levels << lvl
237
+ # profiles << prof
238
+ # end
239
+ # end
240
+ # end
241
+ # [levels, profiles]
242
+ # end
243
+
244
+ # def ruby_class_to_puppet_type(class_name)
245
+ # pup_type = class_name.split('::').last.capitalize
246
+ # case pup_type
247
+ # when %r{(Trueclass|Falseclass)}
248
+ # 'Boolean'
249
+ # when %r{(String|Pathname)}
250
+ # 'String'
251
+ # when %r{(Integer|Fixnum)}
252
+ # 'Integer'
253
+ # when %r{(Float|Double)}
254
+ # 'Float'
255
+ # else
256
+ # pup_type
257
+ # end
258
+ # end
259
+
260
+ def load_mapping_data
261
+ files = case module_name
262
+ when /_windows$/
263
+ cem_windows_mapping_files
264
+ when /_linux$/
265
+ cem_linux_mapping_files
266
+ else
267
+ raise "Module name '#{module_name}' is not a CEM module"
268
+ end
269
+ validate_mapping_files_framework(files).each_with_object({}) do |f, h|
270
+ h[File.basename(f.path, '.yaml')] = YAML.load_file(f.path)
271
+ end
272
+ end
273
+
274
+ def cem_linux_mapping_files
275
+ facts = [['os.name', osname], ['os.release.major', major_version]]
276
+ mapping_files = hiera_conf.local_hiera_files_with_facts(*facts, hierarchy_name: 'Mapping Data')
277
+ raise AbideDevUtils::Errors::MappingFilesNotFoundError, facts if mapping_files.nil? || mapping_files.empty?
278
+
279
+ mapping_files
280
+ end
281
+
282
+ def cem_windows_mapping_files
283
+ facts = ['os.release.major', major_version]
284
+ mapping_files = hiera_conf.local_hiera_files_with_fact(facts[0], facts[1], hierarchy_name: 'Mapping Data')
285
+ raise AbideDevUtils::Errors::MappingFilesNotFoundError, facts if mapping_files.nil? || mapping_files.empty?
286
+
287
+ mapping_files
288
+ end
289
+
290
+ def validate_mapping_files_framework(files)
291
+ validated_files = files.select { |f| f.path_parts.include?(framework) }
292
+ if validated_files.nil? || validated_files.empty?
293
+ raise AbideDevUtils::Errors::MappingDataFrameworkMismatchError, framework
294
+ end
295
+
296
+ validated_files
297
+ end
298
+
299
+ def load_resource_data
300
+ facts = [['os.family', osfamily], ['os.name', osname], ['os.release.major', major_version]]
301
+ rdata_files = hiera_conf.local_hiera_files_with_facts(*facts, hierarchy_name: 'Resource Data')
302
+ raise AbideDevUtils::Errors::ResourceDataNotFoundError, facts if rdata_files.nil? || rdata_files.empty?
303
+
304
+ YAML.load_file(rdata_files[0].path)
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbideDevUtils
4
+ module CEM
5
+ module HieraData; end
6
+ end
7
+ end
@@ -1,19 +1,129 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'abide_dev_utils/cem/hiera_data/mapping_data/map_data'
4
+ require 'abide_dev_utils/cem/hiera_data/mapping_data/mixins'
5
+
3
6
  module AbideDevUtils
4
7
  module CEM
5
8
  module Mapping
9
+ ALL_TYPES = %w[hiera_title_num number hiera_title vulnid title].freeze
10
+ FRAMEWORK_TYPES = {
11
+ 'cis' => %w[hiera_title_num number hiera_title title],
12
+ 'stig' => %w[hiera_title_num number hiera_title vulnid title],
13
+ }.freeze
14
+ CIS_TYPES = %w[hiera_title_num number hiera_title title].freeze
15
+ STIG_TYPES = %w[hiera_title_num number hiera_title vulnid title].freeze
16
+
17
+ # Represents a single map data file
18
+ class MapData
19
+ def initialize(data)
20
+ @raw_data = data
21
+ end
22
+
23
+ def method_missing(meth, *args, &block)
24
+ if data.respond_to?(meth)
25
+ data.send(meth, *args, &block)
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def respond_to_missing?(meth, include_private = false)
32
+ data.respond_to?(meth) || super
33
+ end
34
+
35
+ def find(identifier, level: nil, profile: nil)
36
+ levels.each do |lvl|
37
+ next unless level.nil? || lvl != level
38
+
39
+ data[lvl].each do |prof, prof_data|
40
+ if prof_data.respond_to?(:keys)
41
+ next unless profile.nil? || prof != profile
42
+
43
+ return prof_data[identifier] if prof_data.key?(identifier)
44
+ elsif prof == identifier
45
+ return prof_data
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def get(identifier, level: nil, profile: nil)
52
+ raise "Invalid level: #{level}" unless profile.nil? || levels.include?(level)
53
+ raise "Invalid profile: #{profile}" unless profile.nil? || profiles.include?(profile)
54
+ return find(identifier, level: level, profile: profile) if level.nil? || profile.nil?
55
+
56
+ begin
57
+ data.dig(level, profile, identifier)
58
+ rescue TypeError
59
+ data.dig(level, identifier)
60
+ end
61
+ end
62
+
63
+ def module_name
64
+ top_key_parts[0]
65
+ end
66
+
67
+ def framework
68
+ top_key_parts[2]
69
+ end
70
+
71
+ def type
72
+ top_key_parts[3]
73
+ end
74
+
75
+ def benchmark
76
+ @raw_data[top_key]['benchmark']
77
+ end
78
+
79
+ def levels_and_profiles
80
+ @levels_and_profiles ||= find_levels_and_profiles
81
+ end
82
+
83
+ def levels
84
+ levels_and_profiles[0]
85
+ end
86
+
87
+ def profiles
88
+ levels_and_profiles[1]
89
+ end
90
+
91
+ def top_key
92
+ @top_key ||= @raw_data.keys.first
93
+ end
94
+
95
+ private
96
+
97
+ def top_key_parts
98
+ @top_key_parts ||= top_key.split('::')
99
+ end
100
+
101
+ def data
102
+ @data ||= @raw_data[top_key].reject { |k, _| k == 'benchmark' }
103
+ end
104
+
105
+ def find_levels_and_profiles
106
+ lvls = []
107
+ profs = []
108
+ data.each do |lvl, prof_hash|
109
+ lvls << lvl
110
+ prof_hash.each do |prof, prof_data|
111
+ profs << prof if prof_data.respond_to?(:keys)
112
+ end
113
+ end
114
+ [lvls.flatten.compact.uniq, profs.flatten.compact.uniq]
115
+ end
116
+ end
117
+
6
118
  # Handles interacting with mapping data
7
119
  class Mapper
8
- MAP_TYPES = %w[hiera_title_num number hiera_title vulnid title].freeze
9
-
10
120
  attr_reader :module_name, :framework, :map_data
11
121
 
12
122
  def initialize(module_name, framework, map_data)
13
123
  @module_name = module_name
14
124
  @framework = framework
15
125
  load_framework(@framework)
16
- @map_data = map_data
126
+ @map_data = map_data.map { |_, v| MapData.new(v) }
17
127
  @cache = {}
18
128
  @rule_cache = {}
19
129
  end
@@ -26,28 +136,28 @@ module AbideDevUtils
26
136
  @version ||= benchmark_data['version']
27
137
  end
28
138
 
139
+ def levels
140
+ @levels ||= default_map_data.levels
141
+ end
142
+
143
+ def profiles
144
+ @profiles ||= default_map_data.profiles
145
+ end
146
+
29
147
  def each_like(identifier)
30
- mtype, mtop = map_type_and_top_key(identifier)
31
- map_data[mtype][mtop].each { |key, val| yield key, val }
148
+ identified_map_data(identifier)&.each { |key, val| yield key, val }
32
149
  end
33
150
 
34
151
  def each_with_array_like(identifier)
35
- mtype, mtop = map_type_and_top_key(identifier)
36
- map_data[mtype][mtop].each_with_object([]) { |(key, val), ary| yield [key, val], ary }
152
+ identified_map_data(identifier)&.each_with_object([]) { |(key, val), ary| yield [key, val], ary }
37
153
  end
38
154
 
39
155
  def get(control_id, level: nil, profile: nil)
40
- return cache_get(control_id, level, profile) if cached?(control_id, level, profile)
41
-
42
- value = get_map(control_id, level: level, profile: profile)
43
- return if value.nil? || value.empty?
44
-
45
- cache_set(value, control_id, level, profile)
46
- value
156
+ identified_map_data(control_id)&.get(control_id, level: level, profile: profile)
47
157
  end
48
158
 
49
159
  def map_type(control_id)
50
- return control_id if MAP_TYPES.include?(control_id)
160
+ return control_id if ALL_TYPES.include?(control_id)
51
161
 
52
162
  case control_id
53
163
  when %r{^c[0-9_]+$}
@@ -78,8 +188,22 @@ module AbideDevUtils
78
188
  end
79
189
  end
80
190
 
191
+ def map_data_by_type(map_type)
192
+ found_map_data = map_data.find { |x| x.type == map_type }
193
+ raise "Failed to find map data with type #{map_type}; Meta: #{{framework: framework, module_name: module_name}}" unless found_map_data
194
+
195
+ found_map_data
196
+ end
197
+
198
+ def identified_map_data(identifier, valid_types: ALL_TYPES)
199
+ mtype = map_type(identifier)
200
+ return unless valid_types.include?(mtype)
201
+
202
+ map_data_by_type(mtype)
203
+ end
204
+
81
205
  def map_type_and_top_key(identifier)
82
- mtype = MAP_TYPES.include?(identifier) ? identifier : map_type(identifier)
206
+ mtype = ALL_TYPES.include?(identifier) ? identifier : map_type(identifier)
83
207
  [mtype, map_top_key(mtype)]
84
208
  end
85
209
 
@@ -97,11 +221,15 @@ module AbideDevUtils
97
221
  end
98
222
 
99
223
  def default_map_type
100
- @default_map_type ||= (framework == 'stig' ? 'vulnid' : map_data.keys.first)
224
+ @default_map_type ||= (framework == 'stig' ? 'vulnid' : map_data.first.type)
225
+ end
226
+
227
+ def default_map_data
228
+ @default_map_data ||= map_data.first
101
229
  end
102
230
 
103
231
  def benchmark_data
104
- @benchmark_data ||= map_data[default_map_type][map_top_key(default_map_type)]['benchmark']
232
+ @benchmark_data ||= default_map_data.benchmark
105
233
  end
106
234
 
107
235
  def cache_key(control_id, *args)
@@ -116,17 +244,21 @@ module AbideDevUtils
116
244
  # Mixin module used by Mapper to implement CIS-specific mapping behavior
117
245
  module MixinCIS
118
246
  def get_map(control_id, level: nil, profile: nil, **_)
119
- mtype, mtop = map_type_and_top_key(control_id)
120
- return if mtype == 'vulnid'
247
+ identified_map_data(control_id, valid_types: CIS_TYPES).get(control_id, level: level, profile: profile)
248
+ return unless imdata
121
249
 
122
- return map_data[mtype][mtop][level][profile][control_id] unless level.nil? || profile.nil?
250
+ if level.nil? || profile.nil?
251
+ map_data[mtype][mtop].each do |lvl, profile_hash|
252
+ next if lvl == 'benchmark' || (level && level != lvl)
123
253
 
124
- map_data[mtype][mtop].each do |lvl, profile_hash|
125
- next if lvl == 'benchmark'
254
+ profile_hash.each do |prof, control_hash|
255
+ next if profile && profile != prof
126
256
 
127
- profile_hash.each do |prof, control_hash|
128
- return map_data[mtype][mtop][lvl][prof][control_id] if control_hash.key?(control_id)
257
+ return control_hash[control_id] if control_hash.key?(control_id)
258
+ end
129
259
  end
260
+ else
261
+ imdata[level][profile][control_id]
130
262
  end
131
263
  end
132
264
  end
@@ -135,18 +267,13 @@ module AbideDevUtils
135
267
  module MixinSTIG
136
268
  def get_map(control_id, level: nil, **_)
137
269
  mtype, mtop = map_type_and_top_key(control_id)
270
+ return unless STIG_TYPES.include?(mtype)
138
271
  return map_data[mtype][mtop][level][control_id] unless level.nil?
139
272
 
140
- begin
141
- map_data[mtype][mtop].each do |lvl, control_hash|
142
- next if lvl == 'benchmark'
273
+ map_data[mtype][mtop].each do |lvl, control_hash|
274
+ next if lvl == 'benchmark'
143
275
 
144
- return control_hash[control_id] if control_hash.key?(control_id)
145
- end
146
- rescue NoMethodError => e
147
- require 'pry'
148
- binding.pry
149
- #raise "Control ID: #{control_id}, Level: #{level}, #{e.message}"
276
+ return control_hash[control_id] if control_hash.key?(control_id)
150
277
  end
151
278
  end
152
279
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/ppt'
4
+ require 'abide_dev_utils/cem/benchmark'
5
+
6
+ module AbideDevUtils
7
+ module CEM
8
+ module Validate
9
+ # Validation methods for resource data
10
+ module ResourceData
11
+ class ControlsWithoutMapsError < StandardError; end
12
+
13
+ def self.controls_without_maps(module_dir = Dir.pwd)
14
+ pupmod = AbideDevUtils::Ppt::PuppetModule.new(module_dir)
15
+ benchmarks = AbideDevUtils::CEM::Benchmark.benchmarks_from_puppet_module(pupmod)
16
+ without_maps = benchmarks.each_with_object({}) do |benchmark, hsh|
17
+ puts "Validating #{benchmark.title}..."
18
+ hsh[benchmark.title] = benchmark.controls.each_with_object([]) do |ctrl, no_maps|
19
+ no_maps << ctrl.id unless ctrl.valid_maps?
20
+ end
21
+ end
22
+ err = ['Found controls in resource data without maps.']
23
+ without_maps.each do |key, val|
24
+ next if val.empty?
25
+
26
+ err << val.unshift("#{key}:").join("\n ")
27
+ end
28
+ raise ControlsWithoutMapsError, err.join("\n") unless without_maps.values.all?(&:empty?)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'abide_dev_utils/cem/validate/resource_data'
4
+
5
+ module AbideDevUtils
6
+ module CEM
7
+ # Namespace for CEM validation modules / classes
8
+ module Validate; end
9
+ end
10
+ end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'abide_dev_utils/xccdf'
4
- require 'abide_dev_utils/cem/coverage_report'
5
4
  require 'abide_dev_utils/cem/generate'
6
5
 
7
6
  module AbideDevUtils
@@ -44,9 +44,21 @@ module Abide
44
44
  options.on('-f [FORMAT]', '--format [FORMAT]', 'The format to output the report in (hash, json, yaml)') do |f|
45
45
  @data[:format] = f
46
46
  end
47
+ options.on('-B [BENCHMARK]', '--benchmark [BENCHMARK]', 'Specify the benchmark to show coverage for') do |x|
48
+ @data[:benchmark] = x
49
+ end
50
+ options.on('-P [PROFILE]', '--profile [PROFILE]', 'Specifiy the profile to show coverage for') do |x|
51
+ @data[:profile] = x
52
+ end
53
+ options.on('-L [LEVEL]', '--level [LEVEL]', 'Specify the level to show coverage for') do |l|
54
+ @data[:profile] = l
55
+ end
47
56
  options.on('-I', '--ignore-benchmark-errors', 'Ignores errors while generating benchmark reports') do
48
57
  @data[:ignore_all] = true
49
58
  end
59
+ options.on('-X [XCCDF_DIR]', '--xccdf-dir [XCCDF_DIR]', 'If specified, the coverage report will be correlated with info from the benchmark XCCDF files') do |d|
60
+ @data[:xccdf_dir] = d
61
+ end
50
62
  options.on('-v', '--verbose', 'Will output the report to the console') { @data[:verbose] = true }
51
63
  options.on('-q', '--quiet', 'Will not output anything to the console') { @data[:quiet] = true }
52
64
  end
@@ -56,9 +68,15 @@ module Abide
56
68
  out_format = @data.fetch(:format, 'yaml')
57
69
  quiet = @data.fetch(:quiet, false)
58
70
  console = @data.fetch(:verbose, false) && !quiet
59
- ignore_all = @data.fetch(:ignore_all, false)
71
+ generate_opts = {
72
+ benchmark: @data.fetch(:benchmark),
73
+ profile: @data.fetch(:profile),
74
+ level: @data.fetch(:level),
75
+ ignore_benchmark_errors: @data.fetch(:ignore_all, false),
76
+ xccdf_dir: @data.fetch(:xccdf_dir),
77
+ }
60
78
  AbideDevUtils::Output.simple('Generating coverage report...') unless quiet
61
- coverage = AbideDevUtils::CEM::CoverageReport.basic_coverage(format_func: :to_h, ignore_benchmark_errors: ignore_all)
79
+ coverage = AbideDevUtils::CEM::Generate::CoverageReport.generate(format_func: :to_h, opts: generate_opts)
62
80
  AbideDevUtils::Output.simple("Saving coverage report to #{file_name}...")
63
81
  case out_format
64
82
  when /yaml/i