abide_dev_utils 0.11.0 → 0.12.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 (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