abide_dev_utils 0.4.2 → 0.8.0
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/.gitignore +2 -1
- data/.rubocop.yml +1 -1
- data/CODEOWNERS +1 -0
- data/README.md +34 -0
- data/abide_dev_utils.gemspec +11 -7
- data/itests.rb +138 -0
- data/lib/abide_dev_utils/cli/abstract.rb +2 -0
- data/lib/abide_dev_utils/cli/comply.rb +115 -0
- data/lib/abide_dev_utils/cli/jira.rb +2 -2
- data/lib/abide_dev_utils/cli/puppet.rb +136 -11
- data/lib/abide_dev_utils/cli/xccdf.rb +26 -7
- data/lib/abide_dev_utils/cli.rb +2 -0
- data/lib/abide_dev_utils/comply.rb +498 -0
- data/lib/abide_dev_utils/config.rb +19 -0
- data/lib/abide_dev_utils/errors/comply.rb +17 -0
- data/lib/abide_dev_utils/errors/gcloud.rb +27 -0
- data/lib/abide_dev_utils/errors/general.rb +5 -0
- data/lib/abide_dev_utils/errors/ppt.rb +12 -0
- data/lib/abide_dev_utils/errors/xccdf.rb +8 -0
- data/lib/abide_dev_utils/errors.rb +2 -0
- data/lib/abide_dev_utils/gcloud.rb +22 -0
- data/lib/abide_dev_utils/jira.rb +15 -0
- data/lib/abide_dev_utils/mixins.rb +16 -0
- data/lib/abide_dev_utils/output.rb +7 -3
- data/lib/abide_dev_utils/ppt/api.rb +219 -0
- data/lib/abide_dev_utils/ppt/class_utils.rb +184 -0
- data/lib/abide_dev_utils/ppt/coverage.rb +2 -3
- data/lib/abide_dev_utils/ppt/score_module.rb +162 -0
- data/lib/abide_dev_utils/ppt.rb +138 -49
- data/lib/abide_dev_utils/validate.rb +5 -1
- data/lib/abide_dev_utils/version.rb +1 -1
- data/lib/abide_dev_utils/xccdf.rb +567 -9
- data/lib/abide_dev_utils.rb +1 -0
- metadata +82 -17
- data/lib/abide_dev_utils/utils/general.rb +0 -9
- data/lib/abide_dev_utils/xccdf/cis/hiera.rb +0 -161
- data/lib/abide_dev_utils/xccdf/cis.rb +0 -3
@@ -1,24 +1,582 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'yaml'
|
4
|
+
require 'hashdiff'
|
5
|
+
require 'nokogiri'
|
4
6
|
require 'abide_dev_utils/validate'
|
5
|
-
require 'abide_dev_utils/xccdf
|
7
|
+
require 'abide_dev_utils/errors/xccdf'
|
8
|
+
require 'abide_dev_utils/output'
|
6
9
|
|
7
10
|
module AbideDevUtils
|
11
|
+
# Contains modules and classes for working with XCCDF files
|
8
12
|
module XCCDF
|
9
|
-
|
10
|
-
|
11
|
-
Nokogiri.XML(File.open(xccdf_file))
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.to_hiera(xccdf_file, opts = {})
|
13
|
+
# Converts and xccdf file to a Hiera representation
|
14
|
+
def self.to_hiera(xccdf_file, opts)
|
15
15
|
type = opts.fetch(:type, 'cis')
|
16
16
|
case type.downcase
|
17
17
|
when 'cis'
|
18
|
-
|
18
|
+
Benchmark.new(xccdf_file).to_hiera(**opts)
|
19
19
|
else
|
20
20
|
AbideDevUtils::Output.simple("XCCDF type #{type} is unsupported!")
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
# Diffs two xccdf files
|
25
|
+
def self.diff(file1, file2, opts)
|
26
|
+
bm1 = Benchmark.new(file1)
|
27
|
+
bm2 = Benchmark.new(file2)
|
28
|
+
profile = opts.fetch(:profile, nil)
|
29
|
+
profile_diff = if profile.nil?
|
30
|
+
bm1.diff_profiles(bm2).each do |_, v|
|
31
|
+
v.transform_values! { |x| x.map!(&:to_s) }
|
32
|
+
end
|
33
|
+
else
|
34
|
+
bm1.diff_profiles(bm2)[profile].transform_values! { |x| x.map!(&:to_s) }
|
35
|
+
end
|
36
|
+
profile_key = profile.nil? ? 'all_profiles' : profile
|
37
|
+
{
|
38
|
+
'benchmark' => bm1.diff_title_version(bm2),
|
39
|
+
profile_key => profile_diff
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Common constants and methods included by nearly everything else
|
44
|
+
module Common
|
45
|
+
XPATHS = {
|
46
|
+
benchmark: {
|
47
|
+
all: 'xccdf:Benchmark',
|
48
|
+
title: 'xccdf:Benchmark/xccdf:title',
|
49
|
+
version: 'xccdf:Benchmark/xccdf:version'
|
50
|
+
},
|
51
|
+
cis: {
|
52
|
+
profiles: {
|
53
|
+
all: 'xccdf:Benchmark/xccdf:Profile',
|
54
|
+
relative_title: './xccdf:title',
|
55
|
+
relative_select: './xccdf:select'
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}.freeze
|
59
|
+
CONTROL_PREFIX = /^[\d.]+_/.freeze
|
60
|
+
UNDERSCORED = /(\s|\(|\)|-|\.)/.freeze
|
61
|
+
CIS_NEXT_GEN_WINDOWS = /[Nn]ext_[Gg]eneration_[Ww]indows_[Ss]ecurity/.freeze
|
62
|
+
CIS_CONTROL_NUMBER = /([0-9.]+[0-9]+)/.freeze
|
63
|
+
CIS_LEVEL_CODE = /(?:_|^)([Ll]evel_[0-9]|[Ll]1|[Ll]2|[NnBb][GgLl]|#{CIS_NEXT_GEN_WINDOWS})/.freeze
|
64
|
+
CIS_CONTROL_PARTS = /#{CIS_CONTROL_NUMBER}#{CIS_LEVEL_CODE}?_+([A-Za-z].*)/.freeze
|
65
|
+
CIS_PROFILE_PARTS = /#{CIS_LEVEL_CODE}[_-]+([A-Za-z].*)/.freeze
|
66
|
+
|
67
|
+
def xpath(path)
|
68
|
+
@xml.xpath(path)
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_xccdf(path)
|
72
|
+
AbideDevUtils::Validate.file(path, extension: '.xml')
|
73
|
+
end
|
74
|
+
|
75
|
+
def normalize_string(str)
|
76
|
+
nstr = str.dup.downcase
|
77
|
+
nstr.gsub!(/[^a-z0-9]$/, '')
|
78
|
+
nstr.gsub!(/^[^a-z]/, '')
|
79
|
+
nstr.gsub!(/(?:_|^)([Ll]1_|[Ll]2_|ng_)/, '')
|
80
|
+
nstr.delete!('(/|\\|\+)')
|
81
|
+
nstr.gsub!(UNDERSCORED, '_')
|
82
|
+
nstr.strip!
|
83
|
+
nstr
|
84
|
+
end
|
85
|
+
|
86
|
+
def normalize_profile_name(prof, **_opts)
|
87
|
+
prof_name = normalize_string("profile_#{control_profile_text(prof)}").dup
|
88
|
+
prof_name.gsub!(CIS_NEXT_GEN_WINDOWS, 'ngws')
|
89
|
+
prof_name.delete_suffix!('_environment_general_use')
|
90
|
+
prof_name.delete_suffix!('sensitive_data_environment_limited_functionality')
|
91
|
+
prof_name.strip!
|
92
|
+
prof_name
|
93
|
+
end
|
94
|
+
|
95
|
+
def normalize_control_name(control, number_format: false)
|
96
|
+
return number_normalize_control(control) if number_format
|
97
|
+
|
98
|
+
name_normalize_control(control)
|
99
|
+
end
|
100
|
+
|
101
|
+
def name_normalize_control(control)
|
102
|
+
normalize_string(control_profile_text(control).gsub(CONTROL_PREFIX, ''))
|
103
|
+
end
|
104
|
+
|
105
|
+
def number_normalize_control(control)
|
106
|
+
numpart = CONTROL_PREFIX.match(control_profile_text(control)).to_s.chop.gsub(UNDERSCORED, '_')
|
107
|
+
"c#{numpart}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def text_normalize(control)
|
111
|
+
control_profile_text(control).tr('_', ' ')
|
112
|
+
end
|
113
|
+
|
114
|
+
def profile_parts(profile)
|
115
|
+
parts = control_profile_text(profile).match(CIS_PROFILE_PARTS)
|
116
|
+
raise AbideDevUtils::Errors::ProfilePartsError, profile if parts.nil?
|
117
|
+
|
118
|
+
parts[1].gsub!(/[Ll]evel_/, 'L')
|
119
|
+
parts[1..2]
|
120
|
+
end
|
121
|
+
|
122
|
+
def control_parts(control, parent_level: nil)
|
123
|
+
mdata = control_profile_text(control).match(CIS_CONTROL_PARTS)
|
124
|
+
raise AbideDevUtils::Errors::ControlPartsError, control if mdata.nil?
|
125
|
+
|
126
|
+
mdata[2] = parent_level unless parent_level.nil?
|
127
|
+
mdata[1..3]
|
128
|
+
end
|
129
|
+
|
130
|
+
def control_profile_text(item)
|
131
|
+
return item.raw_title if item.respond_to?(:abide_object?)
|
132
|
+
|
133
|
+
if item.respond_to?(:split)
|
134
|
+
return item.split('benchmarks_rule_')[-1] if item.include?('benchmarks_rule_')
|
135
|
+
|
136
|
+
item.split('benchmarks_profile_')[-1]
|
137
|
+
else
|
138
|
+
return item['idref'].to_s.split('benchmarks_rule_')[-1] if item.name == 'select'
|
139
|
+
|
140
|
+
item['id'].to_s.split('benchmarks_profile_')[-1]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def sorted_control_classes(raw_select_list, sort_key: :number)
|
145
|
+
raw_select_list.map { |x| Control.new(x) }.sort_by(&sort_key)
|
146
|
+
end
|
147
|
+
|
148
|
+
def sorted_profile_classes(raw_profile_list, sort_key: :title)
|
149
|
+
raw_profile_list.map { |x| Profile.new(x) }.sort_by(&sort_key)
|
150
|
+
end
|
151
|
+
|
152
|
+
def ==(other)
|
153
|
+
diff_properties.map { |x| send(x) } == other.diff_properties.map { |x| other.send(x) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def default_diff_opts
|
157
|
+
{
|
158
|
+
similarity: 1,
|
159
|
+
strict: true,
|
160
|
+
strip: true,
|
161
|
+
array_path: true,
|
162
|
+
delimiter: '//',
|
163
|
+
use_lcs: false
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
def diff(other, **opts)
|
168
|
+
Hashdiff.diff(
|
169
|
+
to_h,
|
170
|
+
other.to_h,
|
171
|
+
default_diff_opts.merge(opts)
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
def abide_object?
|
176
|
+
true
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Class representation of an XCCDF benchmark
|
181
|
+
class Benchmark
|
182
|
+
include AbideDevUtils::XCCDF::Common
|
183
|
+
|
184
|
+
attr_reader :xml, :title, :version, :diff_properties
|
185
|
+
|
186
|
+
def initialize(path)
|
187
|
+
@xml = parse(path)
|
188
|
+
@title = xpath('xccdf:Benchmark/xccdf:title').text
|
189
|
+
@version = xpath('xccdf:Benchmark/xccdf:version').text
|
190
|
+
@diff_properties = %i[title version profiles]
|
191
|
+
end
|
192
|
+
|
193
|
+
def normalized_title
|
194
|
+
normalize_string(title)
|
195
|
+
end
|
196
|
+
|
197
|
+
def profiles
|
198
|
+
@profiles ||= Profiles.new(xpath('xccdf:Benchmark/xccdf:Profile'))
|
199
|
+
end
|
200
|
+
|
201
|
+
def profile_levels
|
202
|
+
@profiles.levels
|
203
|
+
end
|
204
|
+
|
205
|
+
def profile_titles
|
206
|
+
@profiles.titles
|
207
|
+
end
|
208
|
+
|
209
|
+
def controls
|
210
|
+
@controls ||= Controls.new(xpath('//xccdf:select'))
|
211
|
+
end
|
212
|
+
|
213
|
+
def controls_by_profile_level(level_code)
|
214
|
+
profiles.select { |x| x.level == level_code }.map(&:controls).flatten.uniq
|
215
|
+
end
|
216
|
+
|
217
|
+
def controls_by_profile_title(profile_title)
|
218
|
+
profiles.select { |x| x.title == profile_title }.controls
|
219
|
+
end
|
220
|
+
|
221
|
+
def find_cis_recommendation(name, number_format: false)
|
222
|
+
profiles.each do |profile|
|
223
|
+
profile.controls.each do |ctrl|
|
224
|
+
return [profile, ctrl] if normalize_control_name(ctrl, number_format: number_format) == name
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def to_h
|
230
|
+
{
|
231
|
+
title: title,
|
232
|
+
version: version,
|
233
|
+
profiles: profiles.to_h
|
234
|
+
}
|
235
|
+
end
|
236
|
+
|
237
|
+
def diff_title_version(other)
|
238
|
+
Hashdiff.diff(
|
239
|
+
to_h.reject { |k, _| k.to_s == 'profiles' },
|
240
|
+
other.to_h.reject { |k, _| k.to_s == 'profiles' },
|
241
|
+
default_diff_opts
|
242
|
+
)
|
243
|
+
end
|
244
|
+
|
245
|
+
def diff_profiles(other)
|
246
|
+
this_diff = {}
|
247
|
+
other_hash = other.to_h[:profiles]
|
248
|
+
to_h[:profiles].each do |name, data|
|
249
|
+
diff_h = Hashdiff.diff(data, other_hash[name], default_diff_opts).each_with_object({}) do |x, a|
|
250
|
+
val_to = x.length == 4 ? x[3] : nil
|
251
|
+
a_key = x[2].is_a?(Hash) ? x[2][:title] : x[2]
|
252
|
+
a[a_key] = [] unless a.key?(a_key)
|
253
|
+
a[a_key] << ChangeSet.new(change: x[0], key: x[1], value: x[2], value_to: val_to)
|
254
|
+
end
|
255
|
+
this_diff[name] = diff_h
|
256
|
+
end
|
257
|
+
this_diff
|
258
|
+
end
|
259
|
+
|
260
|
+
def diff_controls(other)
|
261
|
+
controls.diff(other.controls)
|
262
|
+
end
|
263
|
+
|
264
|
+
# Converts object to Hiera-formatted YAML
|
265
|
+
# @return [String] YAML-formatted string
|
266
|
+
def to_hiera(parent_key_prefix: nil, num: false, levels: [], titles: [], **_kwargs)
|
267
|
+
hash = { 'title' => title, 'version' => version }
|
268
|
+
key_prefix = hiera_parent_key(parent_key_prefix)
|
269
|
+
profiles.each do |profile|
|
270
|
+
next unless levels.empty? || levels.include?(profile.level)
|
271
|
+
next unless titles.empty? || titles.include?(profile.title)
|
272
|
+
|
273
|
+
hash[profile.hiera_title] = hiera_controls_for_profile(profile, num)
|
274
|
+
end
|
275
|
+
hash.transform_keys! do |k|
|
276
|
+
[key_prefix, k].join('::').strip
|
277
|
+
end
|
278
|
+
hash.to_yaml
|
279
|
+
end
|
280
|
+
|
281
|
+
def resolve_control_reference(control)
|
282
|
+
xpath("//xccdf:Rule[@id='#{control.reference}']")
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
def parse(path)
|
288
|
+
validate_xccdf(path)
|
289
|
+
Nokogiri::XML.parse(File.open(File.expand_path(path))) do |config|
|
290
|
+
config.strict.noblanks.norecover
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def sorted_profile_classes(raw_profile_list, sort_key: :level)
|
295
|
+
raw_profile_list.map { |x| Profile.new(x) }.sort_by(&sort_key)
|
296
|
+
end
|
297
|
+
|
298
|
+
def find_profiles
|
299
|
+
profs = {}
|
300
|
+
xpath('xccdf:Benchmark/xccdf:Profile').each do |profile|
|
301
|
+
level_code, name = profile_parts(profile['id'])
|
302
|
+
profs[name] = {} unless profs.key?(name)
|
303
|
+
profs[name][level_code] = profile
|
304
|
+
end
|
305
|
+
profs
|
306
|
+
end
|
307
|
+
|
308
|
+
def find_profile_names
|
309
|
+
profiles.each_with_object([]) do |profile, ary|
|
310
|
+
ary << "#{profile.level} #{profile.plain_text_title}"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def hiera_controls_for_profile(profile, number_format)
|
315
|
+
profile.controls.each_with_object([]) do |ctrl, ary|
|
316
|
+
ary << ctrl.hiera_title(number_format: number_format)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
def hiera_parent_key(prefix)
|
321
|
+
return normalized_title if prefix.nil?
|
322
|
+
|
323
|
+
prefix.end_with?('::') ? "#{prefix}#{normalized_title}" : "#{prefix}::#{normalized_title}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
class ChangeSet
|
328
|
+
attr_reader :change, :key, :value, :value_to
|
329
|
+
|
330
|
+
def initialize(change:, key:, value:, value_to: nil)
|
331
|
+
validate_change(change)
|
332
|
+
@change = change
|
333
|
+
@key = key
|
334
|
+
@value = value
|
335
|
+
@value_to = value_to
|
336
|
+
end
|
337
|
+
|
338
|
+
def to_s
|
339
|
+
val_to_str = value_to.nil? ? ' ' : " to #{value_to} "
|
340
|
+
"#{change_string} value #{value}#{val_to_str}at #{key}"
|
341
|
+
end
|
342
|
+
|
343
|
+
def can_merge?(other)
|
344
|
+
return false unless (change == '-' && other.change == '+') || (change == '+' && other.change == '-')
|
345
|
+
return false unless key == other.key || value_hash_equality(other)
|
346
|
+
|
347
|
+
true
|
348
|
+
end
|
349
|
+
|
350
|
+
def merge(other)
|
351
|
+
unless can_merge?(other)
|
352
|
+
raise ArgumentError, 'Cannot merge. Possible causes: change is identical; key or value do not match'
|
353
|
+
end
|
354
|
+
|
355
|
+
new_to_value = value == other.value ? nil : other.value
|
356
|
+
ChangeSet.new(
|
357
|
+
change: '~',
|
358
|
+
key: key,
|
359
|
+
value: value,
|
360
|
+
value_to: new_to_value
|
361
|
+
)
|
362
|
+
end
|
363
|
+
|
364
|
+
private
|
365
|
+
|
366
|
+
def value_hash_equality(other)
|
367
|
+
equality = false
|
368
|
+
value.each do |k, v|
|
369
|
+
equality = true if v == other.value[k]
|
370
|
+
end
|
371
|
+
equality
|
372
|
+
end
|
373
|
+
|
374
|
+
def validate_change(change)
|
375
|
+
raise ArgumentError, "Change type #{change} in invalid" unless ['+', '-', '~'].include?(change)
|
376
|
+
end
|
377
|
+
|
378
|
+
def change_string
|
379
|
+
case change
|
380
|
+
when '-'
|
381
|
+
'remove'
|
382
|
+
when '+'
|
383
|
+
'add'
|
384
|
+
else
|
385
|
+
'change'
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
class ObjectContainer
|
391
|
+
include AbideDevUtils::XCCDF::Common
|
392
|
+
|
393
|
+
def initialize(list, object_creation_method, *args, **kwargs)
|
394
|
+
@object_list = send(object_creation_method.to_sym, list, *args, **kwargs)
|
395
|
+
@searchable = []
|
396
|
+
end
|
397
|
+
|
398
|
+
def method_missing(m, *args, &block)
|
399
|
+
property = m.to_s.start_with?('search_') ? m.to_s.split('_')[-1].to_sym : nil
|
400
|
+
return search(property, *args, &block) if property && @searchable.include?(property)
|
401
|
+
return @object_list.send(m, *args, &block) if @object_list.respond_to?(m)
|
402
|
+
|
403
|
+
super
|
404
|
+
end
|
405
|
+
|
406
|
+
def respond_to_missing?(m, include_private = false)
|
407
|
+
return true if m.to_s.start_with?('search_') && @searchable.include?(m.to_s.split('_')[-1].to_sym)
|
408
|
+
|
409
|
+
super
|
410
|
+
end
|
411
|
+
|
412
|
+
def to_h
|
413
|
+
@object_list.each_with_object({}) do |obj, self_hash|
|
414
|
+
key = resolve_hash_key(obj)
|
415
|
+
self_hash[key] = obj.to_h
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def search(property, item)
|
420
|
+
max = @object_list.length - 1
|
421
|
+
min = 0
|
422
|
+
while min <= max
|
423
|
+
mid = (min + max) / 2
|
424
|
+
return @object_list[mid] if @object_list[mid].send(property.to_sym) == item
|
425
|
+
|
426
|
+
if @object_list[mid].send(property.to_sym) > item
|
427
|
+
max = mid - 1
|
428
|
+
else
|
429
|
+
min = mid + 1
|
430
|
+
end
|
431
|
+
end
|
432
|
+
nil
|
433
|
+
end
|
434
|
+
|
435
|
+
private
|
436
|
+
|
437
|
+
def resolve_hash_key(obj)
|
438
|
+
return obj.send(:raw_title) unless defined?(@hash_key)
|
439
|
+
|
440
|
+
@hash_key.each_with_object([]) { |x, a| a << obj.send(x) }.join('_')
|
441
|
+
end
|
442
|
+
|
443
|
+
def searchable!(*properties)
|
444
|
+
@searchable = properties
|
445
|
+
end
|
446
|
+
|
447
|
+
def index!(property)
|
448
|
+
@index = property
|
449
|
+
end
|
450
|
+
|
451
|
+
def hash_key!(*properties)
|
452
|
+
@hash_key = properties
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
class Profiles < ObjectContainer
|
457
|
+
def initialize(list)
|
458
|
+
super(list, :sorted_profile_classes)
|
459
|
+
searchable! :level, :title
|
460
|
+
index! :title
|
461
|
+
hash_key! :level, :title
|
462
|
+
end
|
463
|
+
|
464
|
+
def levels
|
465
|
+
@levels ||= @object_list.map(&:level).sort
|
466
|
+
end
|
467
|
+
|
468
|
+
def titles
|
469
|
+
@titles ||= @object_list.map(&:title).sort
|
470
|
+
end
|
471
|
+
|
472
|
+
def include_level?(item)
|
473
|
+
levels.include?(item)
|
474
|
+
end
|
475
|
+
|
476
|
+
def include_title?(item)
|
477
|
+
titles.include?(item)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
class Controls < ObjectContainer
|
482
|
+
def initialize(list)
|
483
|
+
super(list, :sorted_control_classes)
|
484
|
+
searchable! :level, :title, :number
|
485
|
+
index! :number
|
486
|
+
hash_key! :number
|
487
|
+
end
|
488
|
+
|
489
|
+
def numbers
|
490
|
+
@numbers ||= @object_list.map(&:number).sort
|
491
|
+
end
|
492
|
+
|
493
|
+
def levels
|
494
|
+
@levels ||= @object_list.map(&:level).sort
|
495
|
+
end
|
496
|
+
|
497
|
+
def titles
|
498
|
+
@titles ||= @object_list.map(&:title).sort
|
499
|
+
end
|
500
|
+
|
501
|
+
def include_number?(item)
|
502
|
+
numbers.include?(item)
|
503
|
+
end
|
504
|
+
|
505
|
+
def include_level?(item)
|
506
|
+
levels.include?(item)
|
507
|
+
end
|
508
|
+
|
509
|
+
def include_title?(item)
|
510
|
+
titles.include?(item)
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
class XccdfElement
|
515
|
+
include AbideDevUtils::XCCDF::Common
|
516
|
+
|
517
|
+
def initialize(element)
|
518
|
+
@xml = element
|
519
|
+
@element_type = self.class.name.split('::').last.downcase
|
520
|
+
@raw_title = control_profile_text(element)
|
521
|
+
end
|
522
|
+
|
523
|
+
def to_h
|
524
|
+
@properties.each_with_object({}) do |pair, hash|
|
525
|
+
hash[pair[0]] = if pair[1].nil?
|
526
|
+
send(pair[0])
|
527
|
+
else
|
528
|
+
obj = send(pair[0])
|
529
|
+
obj.send(pair[1])
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def to_s
|
535
|
+
@hash.inspect
|
536
|
+
end
|
537
|
+
|
538
|
+
def reference
|
539
|
+
@reference ||= @element_type == 'control' ? @xml['idref'] : @xml['id']
|
540
|
+
end
|
541
|
+
|
542
|
+
def hiera_title(**opts)
|
543
|
+
send("normalize_#{@element_type}_name".to_sym, @xml, **opts)
|
544
|
+
end
|
545
|
+
|
546
|
+
private
|
547
|
+
|
548
|
+
attr_reader :xml
|
549
|
+
|
550
|
+
def properties(*plain_props, **props)
|
551
|
+
plain_props.each { |x| props[x] = nil }
|
552
|
+
props.transform_keys!(&:to_sym)
|
553
|
+
self.class.class_eval do
|
554
|
+
attr_reader :raw_title, :diff_properties
|
555
|
+
|
556
|
+
plain_props.each { |x| attr_reader x.to_sym unless respond_to?(x) }
|
557
|
+
props.each_key { |k| attr_reader k.to_sym unless respond_to?(k) }
|
558
|
+
end
|
559
|
+
@diff_properties = props.keys
|
560
|
+
@properties = props
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
class Profile < XccdfElement
|
565
|
+
def initialize(profile)
|
566
|
+
super(profile)
|
567
|
+
@level, @title = profile_parts(control_profile_text(profile))
|
568
|
+
@plain_text_title = @xml.xpath('./xccdf:title').text
|
569
|
+
@controls = Controls.new(xpath('./xccdf:select'))
|
570
|
+
properties :title, :level, :plain_text_title, controls: :to_h
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
class Control < XccdfElement
|
575
|
+
def initialize(control, parent_level: nil)
|
576
|
+
super(control)
|
577
|
+
@number, @level, @title = control_parts(control_profile_text(control), parent_level: parent_level)
|
578
|
+
properties :number, :level, :title
|
579
|
+
end
|
580
|
+
end
|
23
581
|
end
|
24
582
|
end
|
data/lib/abide_dev_utils.rb
CHANGED