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,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
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
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
+
6
+ module AbideDevUtils
7
+ module CEM
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
+
118
+ # Handles interacting with mapping data
119
+ class Mapper
120
+ attr_reader :module_name, :framework, :map_data
121
+
122
+ def initialize(module_name, framework, map_data)
123
+ @module_name = module_name
124
+ @framework = framework
125
+ load_framework(@framework)
126
+ @map_data = map_data.map { |_, v| MapData.new(v) }
127
+ @cache = {}
128
+ @rule_cache = {}
129
+ end
130
+
131
+ def title
132
+ @title ||= benchmark_data['title']
133
+ end
134
+
135
+ def version
136
+ @version ||= benchmark_data['version']
137
+ end
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
+
147
+ def each_like(identifier)
148
+ identified_map_data(identifier)&.each { |key, val| yield key, val }
149
+ end
150
+
151
+ def each_with_array_like(identifier)
152
+ identified_map_data(identifier)&.each_with_object([]) { |(key, val), ary| yield [key, val], ary }
153
+ end
154
+
155
+ def get(control_id, level: nil, profile: nil)
156
+ identified_map_data(control_id)&.get(control_id, level: level, profile: profile)
157
+ end
158
+
159
+ def map_type(control_id)
160
+ return control_id if ALL_TYPES.include?(control_id)
161
+
162
+ case control_id
163
+ when %r{^c[0-9_]+$}
164
+ 'hiera_title_num'
165
+ when %r{^[0-9][0-9.]*$}
166
+ 'number'
167
+ when %r{^[a-z][a-z0-9_]+$}
168
+ 'hiera_title'
169
+ when %r{^V-[0-9]{6}$}
170
+ 'vulnid'
171
+ else
172
+ 'title'
173
+ end
174
+ end
175
+
176
+ private
177
+
178
+ def load_framework(framework)
179
+ case framework.downcase
180
+ when 'cis'
181
+ self.class.include AbideDevUtils::CEM::Mapping::MixinCIS
182
+ extend AbideDevUtils::CEM::Mapping::MixinCIS
183
+ when 'stig'
184
+ self.class.include AbideDevUtils::CEM::Mapping::MixinSTIG
185
+ extend AbideDevUtils::CEM::Mapping::MixinSTIG
186
+ else
187
+ raise "Invalid framework: #{framework}"
188
+ end
189
+ end
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
+
205
+ def map_type_and_top_key(identifier)
206
+ mtype = ALL_TYPES.include?(identifier) ? identifier : map_type(identifier)
207
+ [mtype, map_top_key(mtype)]
208
+ end
209
+
210
+ def cached?(control_id, *args)
211
+ @cache.key?(cache_key(control_id, *args))
212
+ end
213
+
214
+ def cache_get(control_id, *args)
215
+ ckey = cache_key(control_id, *args)
216
+ @cache[ckey] if cached?(control_id, *args)
217
+ end
218
+
219
+ def cache_set(value, control_id, *args)
220
+ @cache[cache_key(control_id, *args)] = value unless value.nil?
221
+ end
222
+
223
+ def default_map_type
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
229
+ end
230
+
231
+ def benchmark_data
232
+ @benchmark_data ||= default_map_data.benchmark
233
+ end
234
+
235
+ def cache_key(control_id, *args)
236
+ args.unshift(control_id).compact.join('-')
237
+ end
238
+
239
+ def map_top_key(mtype)
240
+ [module_name, 'mappings', framework, mtype].join('::')
241
+ end
242
+ end
243
+
244
+ # Mixin module used by Mapper to implement CIS-specific mapping behavior
245
+ module MixinCIS
246
+ def get_map(control_id, level: nil, profile: nil, **_)
247
+ identified_map_data(control_id, valid_types: CIS_TYPES).get(control_id, level: level, profile: profile)
248
+ return unless imdata
249
+
250
+ if level.nil? || profile.nil?
251
+ map_data[mtype][mtop].each do |lvl, profile_hash|
252
+ next if lvl == 'benchmark' || (level && level != lvl)
253
+
254
+ profile_hash.each do |prof, control_hash|
255
+ next if profile && profile != prof
256
+
257
+ return control_hash[control_id] if control_hash.key?(control_id)
258
+ end
259
+ end
260
+ else
261
+ imdata[level][profile][control_id]
262
+ end
263
+ end
264
+ end
265
+
266
+ # Mixin module used by Mapper to implement STIG-specific mapping behavior
267
+ module MixinSTIG
268
+ def get_map(control_id, level: nil, **_)
269
+ mtype, mtop = map_type_and_top_key(control_id)
270
+ return unless STIG_TYPES.include?(mtype)
271
+ return map_data[mtype][mtop][level][control_id] unless level.nil?
272
+
273
+ map_data[mtype][mtop].each do |lvl, control_hash|
274
+ next if lvl == 'benchmark'
275
+
276
+ return control_hash[control_id] if control_hash.key?(control_id)
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
282
+ 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,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'abide_dev_utils/xccdf'
4
+ require 'abide_dev_utils/cem/generate'
4
5
 
5
6
  module AbideDevUtils
6
7
  # Methods for working with Compliance Enforcement Modules (CEM)