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.
- checksums.yaml +4 -4
- data/Gemfile.lock +18 -31
- data/lib/abide_dev_utils/cem/benchmark.rb +335 -136
- data/lib/abide_dev_utils/cem/generate/coverage_report.rb +380 -0
- data/lib/abide_dev_utils/cem/generate/reference.rb +238 -35
- data/lib/abide_dev_utils/cem/generate.rb +5 -4
- data/lib/abide_dev_utils/cem/hiera_data/mapping_data/map_data.rb +110 -0
- data/lib/abide_dev_utils/cem/hiera_data/mapping_data/mixins.rb +46 -0
- data/lib/abide_dev_utils/cem/hiera_data/mapping_data.rb +146 -0
- data/lib/abide_dev_utils/cem/hiera_data/resource_data/control.rb +127 -0
- data/lib/abide_dev_utils/cem/hiera_data/resource_data/parameters.rb +90 -0
- data/lib/abide_dev_utils/cem/hiera_data/resource_data/resource.rb +102 -0
- data/lib/abide_dev_utils/cem/hiera_data/resource_data.rb +310 -0
- data/lib/abide_dev_utils/cem/hiera_data.rb +7 -0
- data/lib/abide_dev_utils/cem/mapping/mapper.rb +161 -34
- data/lib/abide_dev_utils/cem/validate/resource_data.rb +33 -0
- data/lib/abide_dev_utils/cem/validate.rb +10 -0
- data/lib/abide_dev_utils/cem.rb +0 -1
- data/lib/abide_dev_utils/cli/cem.rb +20 -2
- data/lib/abide_dev_utils/dot_number_comparable.rb +75 -0
- data/lib/abide_dev_utils/errors/cem.rb +10 -0
- data/lib/abide_dev_utils/ppt/class_utils.rb +1 -1
- data/lib/abide_dev_utils/ppt/code_gen/data_types.rb +64 -0
- data/lib/abide_dev_utils/ppt/code_gen/generate.rb +15 -0
- data/lib/abide_dev_utils/ppt/code_gen/resource.rb +59 -0
- data/lib/abide_dev_utils/ppt/code_gen/resource_types/base.rb +93 -0
- data/lib/abide_dev_utils/ppt/code_gen/resource_types/class.rb +17 -0
- data/lib/abide_dev_utils/ppt/code_gen/resource_types/manifest.rb +16 -0
- data/lib/abide_dev_utils/ppt/code_gen/resource_types/parameter.rb +16 -0
- data/lib/abide_dev_utils/ppt/code_gen/resource_types/strings.rb +13 -0
- data/lib/abide_dev_utils/ppt/code_gen/resource_types.rb +6 -0
- data/lib/abide_dev_utils/ppt/code_gen.rb +15 -0
- data/lib/abide_dev_utils/ppt/code_introspection.rb +102 -0
- data/lib/abide_dev_utils/ppt/hiera.rb +4 -1
- data/lib/abide_dev_utils/ppt/puppet_module.rb +2 -1
- data/lib/abide_dev_utils/ppt.rb +3 -0
- data/lib/abide_dev_utils/version.rb +1 -1
- data/lib/abide_dev_utils/xccdf/parser/helpers.rb +146 -0
- data/lib/abide_dev_utils/xccdf/parser/objects.rb +87 -144
- data/lib/abide_dev_utils/xccdf/parser.rb +5 -0
- data/lib/abide_dev_utils/xccdf/utils.rb +89 -0
- data/lib/abide_dev_utils/xccdf.rb +193 -63
- metadata +27 -3
- 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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
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 =
|
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.
|
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 ||=
|
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
|
-
|
120
|
-
return
|
247
|
+
identified_map_data(control_id, valid_types: CIS_TYPES).get(control_id, level: level, profile: profile)
|
248
|
+
return unless imdata
|
121
249
|
|
122
|
-
|
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
|
-
|
125
|
-
|
254
|
+
profile_hash.each do |prof, control_hash|
|
255
|
+
next if profile && profile != prof
|
126
256
|
|
127
|
-
|
128
|
-
|
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
|
-
|
141
|
-
|
142
|
-
next if lvl == 'benchmark'
|
273
|
+
map_data[mtype][mtop].each do |lvl, control_hash|
|
274
|
+
next if lvl == 'benchmark'
|
143
275
|
|
144
|
-
|
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
|
data/lib/abide_dev_utils/cem.rb
CHANGED
@@ -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
|
-
|
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.
|
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
|