abide_dev_utils 0.14.1 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +24 -38
- data/abide_dev_utils.gemspec +0 -1
- data/lib/abide_dev_utils/cem/benchmark.rb +0 -2
- data/lib/abide_dev_utils/cli/cem.rb +9 -8
- data/lib/abide_dev_utils/cli/jira.rb +48 -4
- data/lib/abide_dev_utils/cli/xccdf.rb +7 -13
- data/lib/abide_dev_utils/config.rb +3 -6
- data/lib/abide_dev_utils/files.rb +16 -6
- data/lib/abide_dev_utils/jira.rb +69 -1
- data/lib/abide_dev_utils/ppt/hiera.rb +7 -24
- data/lib/abide_dev_utils/prompt.rb +3 -1
- data/lib/abide_dev_utils/version.rb +1 -1
- data/lib/abide_dev_utils/xccdf/diff.rb +64 -200
- data/lib/abide_dev_utils/xccdf/parser/helpers.rb +0 -93
- data/lib/abide_dev_utils/xccdf/parser/objects/diffable_object.rb +347 -0
- data/lib/abide_dev_utils/xccdf/parser/objects.rb +413 -89
- data/lib/abide_dev_utils/xccdf/parser.rb +8 -9
- data/lib/abide_dev_utils/xccdf.rb +1 -10
- metadata +4 -24
- data/lib/abide_dev_utils/xccdf/diff/benchmark/number_title.rb +0 -270
- data/lib/abide_dev_utils/xccdf/diff/benchmark/profile.rb +0 -104
- data/lib/abide_dev_utils/xccdf/diff/benchmark/property.rb +0 -127
- data/lib/abide_dev_utils/xccdf/diff/benchmark/property_existence.rb +0 -47
- data/lib/abide_dev_utils/xccdf/diff/benchmark.rb +0 -267
- data/lib/abide_dev_utils/xccdf/diff/utils.rb +0 -30
- data/lib/abide_dev_utils/xccdf/parser/objects/digest_object.rb +0 -118
@@ -1,267 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'abide_dev_utils/xccdf/diff/benchmark/number_title'
|
4
|
-
require 'abide_dev_utils/xccdf/diff/benchmark/property'
|
5
|
-
require 'abide_dev_utils/xccdf/diff/utils'
|
6
|
-
require 'abide_dev_utils/xccdf/parser'
|
7
|
-
|
8
|
-
module AbideDevUtils
|
9
|
-
module XCCDF
|
10
|
-
# Holds methods and classes used to diff XCCDF-derived objects.
|
11
|
-
module Diff
|
12
|
-
# Class for benchmark diffs
|
13
|
-
class BenchmarkDiff
|
14
|
-
include AbideDevUtils::XCCDF::Diff::BenchmarkPropertyDiff
|
15
|
-
attr_reader :self, :other, :opts
|
16
|
-
|
17
|
-
DEFAULT_OPTS = {
|
18
|
-
only_classes: %w[rule],
|
19
|
-
}.freeze
|
20
|
-
|
21
|
-
# Used for filtering by level and profile
|
22
|
-
LVL_PROF_DEFAULT = [nil, nil].freeze
|
23
|
-
|
24
|
-
# @param xml1 [String] path to the first benchmark XCCDF xml file
|
25
|
-
# @param xml2 [String] path to the second benchmark XCCDF xml file
|
26
|
-
# @param opts [Hash] options hash
|
27
|
-
def initialize(xml1, xml2, opts = {})
|
28
|
-
@self = new_benchmark(xml1)
|
29
|
-
@other = new_benchmark(xml2)
|
30
|
-
@opts = DEFAULT_OPTS.merge(DEFAULT_PROPERTY_DIFF_OPTS).merge(opts)
|
31
|
-
@levels = []
|
32
|
-
@profiles = []
|
33
|
-
end
|
34
|
-
|
35
|
-
def method_missing(method_name, *args, &block)
|
36
|
-
if opts.key?(method_name)
|
37
|
-
opts[method_name]
|
38
|
-
elsif @diff&.key?(method_name)
|
39
|
-
@diff[method_name]
|
40
|
-
else
|
41
|
-
super
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def respond_to_missing?(method_name, include_private = false)
|
46
|
-
opts.key?(method_name) || @diff&.key?(method_name) || super
|
47
|
-
end
|
48
|
-
|
49
|
-
# Memoized getter for all "numbered" children for the "self" benchmark based on optional filters in opts
|
50
|
-
def self_numbered_children
|
51
|
-
@self_numbered_children ||= find_all_numbered_children(@self,
|
52
|
-
only_classes: opts[:only_classes],
|
53
|
-
level: opts[:level],
|
54
|
-
profile: opts[:profile])
|
55
|
-
end
|
56
|
-
|
57
|
-
# Memoized getter for all "numbered" children for the "other" benchmark based on optional filters in opts
|
58
|
-
def other_numbered_children
|
59
|
-
@other_numbered_children ||= find_all_numbered_children(@other,
|
60
|
-
only_classes: opts[:only_classes],
|
61
|
-
level: opts[:level],
|
62
|
-
profile: opts[:profile])
|
63
|
-
end
|
64
|
-
|
65
|
-
# Basic title diff
|
66
|
-
def numbered_children_title_diff
|
67
|
-
{
|
68
|
-
self: self_numbered_children.map { |c| c.title.to_s } - other_numbered_children.map { |c| c.title.to_s },
|
69
|
-
other: other_numbered_children.map { |c| c.title.to_s } - self_numbered_children.map { |c| c.title.to_s },
|
70
|
-
}
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns the output of a NumberTitleDiff object's diff function based on self_numbered_children and other_numbered_children
|
74
|
-
def number_title_diff
|
75
|
-
NumberTitleDiff.new(self_numbered_children, other_numbered_children).diff
|
76
|
-
end
|
77
|
-
|
78
|
-
# Hash of data about the "self" benchmark and the diff parameters
|
79
|
-
def from_benchmark
|
80
|
-
@from_benchmark ||= from_to_hash(@self)
|
81
|
-
end
|
82
|
-
|
83
|
-
# Hash of data about the "other" benchmark and the diff parameters
|
84
|
-
def to_benchmark
|
85
|
-
@to_benchmark ||= from_to_hash(@other)
|
86
|
-
end
|
87
|
-
|
88
|
-
# All levels that numbered children have been filtered on
|
89
|
-
def levels
|
90
|
-
@levels.flatten.uniq.empty? ? [:all] : @levels.flatten.uniq
|
91
|
-
end
|
92
|
-
|
93
|
-
# All profiles that numbered children have been filtered on
|
94
|
-
def profiles
|
95
|
-
@profiles.flatten.uniq.empty? ? [:all] : @profiles.flatten.uniq
|
96
|
-
end
|
97
|
-
|
98
|
-
# Returns a diff of the changes from the "self" xml (xml1) to the "other" xml (xml2)
|
99
|
-
# This function is memoized because the diff operation is expensive. To run the diff
|
100
|
-
# operation again, set the `new` parameter to `true`
|
101
|
-
# @param new [Boolean] Set to `true` to force a new diff operation
|
102
|
-
# return [Hash] the diff in hash format
|
103
|
-
def diff(new: false)
|
104
|
-
return @diff if @diff && !new
|
105
|
-
|
106
|
-
@diff = {}
|
107
|
-
@diff[:number_title] = number_title_diff
|
108
|
-
{ from: from_benchmark, to: to_benchmark, diff: @diff }
|
109
|
-
end
|
110
|
-
|
111
|
-
private
|
112
|
-
|
113
|
-
# Returns a Benchmark object from a XCCDF xml file path
|
114
|
-
# @param xml [String] path to a XCCDF xml file
|
115
|
-
def new_benchmark(xml)
|
116
|
-
AbideDevUtils::XCCDF::Parser.parse(xml)
|
117
|
-
end
|
118
|
-
|
119
|
-
# Returns a hash of benchmark data
|
120
|
-
# @param obj [AbideDevUtils::XCCDF::Parser::Objects::Benchmark]
|
121
|
-
# @return [Hash] diff-relevant benchmark information
|
122
|
-
def from_to_hash(obj)
|
123
|
-
{
|
124
|
-
title: obj.title.to_s,
|
125
|
-
version: obj.version.to_s,
|
126
|
-
compared: {
|
127
|
-
levels: levels,
|
128
|
-
profiles: profiles,
|
129
|
-
}
|
130
|
-
}
|
131
|
-
end
|
132
|
-
|
133
|
-
# Function to check if a numbered child meets inclusion criteria based on filtering
|
134
|
-
# options.
|
135
|
-
# @param child [Object] XCCDF parser object that includes AbideDevUtils::XCCDF::Parser::Objects::NumberedObject.
|
136
|
-
# @param only_classes [Array] class names as strings. When this is specified, only objects with those class names will be considered.
|
137
|
-
# @param level [String] Specifies the benchmark profile level to filter children on. Only applies to Rules linked to Profiles that have levels.
|
138
|
-
# @param profile [String] Specifies the benchmark profile to filter children on. Only applies to Rules that have linked Profiles.
|
139
|
-
# @return [TrueClass] if child meets all filtering criteria and should be included in the set.
|
140
|
-
# @return [FalseClass] if child does not meet all criteria and should be excluded from the set.
|
141
|
-
def include_numbered_child?(child, only_classes: [], level: nil, profile: nil)
|
142
|
-
return false unless valid_class?(child, only_classes)
|
143
|
-
return true if level.nil? && profile.nil?
|
144
|
-
|
145
|
-
validated_props = valid_profile_and_level(child, level, profile)
|
146
|
-
should_include = validated_props.none?(&:nil?)
|
147
|
-
new_validated_props_vars(validated_props) if should_include
|
148
|
-
should_include
|
149
|
-
end
|
150
|
-
|
151
|
-
# Adds level and profile to respective instance vars
|
152
|
-
# @param validated_props [Array] two item array: first item - profile level, second item - profile title
|
153
|
-
def new_validated_props_vars(validated_props)
|
154
|
-
@levels << validated_props[0]
|
155
|
-
@profiles << validated_props[1]
|
156
|
-
end
|
157
|
-
|
158
|
-
# Checks if the child's class is in the only_classes list, if applicable
|
159
|
-
# @param child [Object] the child whose class will be checked
|
160
|
-
# @param only_classes [Array] an array of class names as strings
|
161
|
-
# @return [TrueClass] if only_classes is empty or if child's class is in only_classes
|
162
|
-
# @return [FalseClass] if only_classes is not empty and child's class is not in only_classes
|
163
|
-
def valid_class?(child, only_classes = [])
|
164
|
-
only_classes.empty? || only_classes.include?(child.label)
|
165
|
-
end
|
166
|
-
|
167
|
-
# Returns a two-item array of a valid level and a valid profile
|
168
|
-
# @param child [Object] XCCDF parser object or array of XCCDF parser objects
|
169
|
-
# @param level [String] a profile level
|
170
|
-
# @param profile [String] a partial / full profile title
|
171
|
-
# @return [Array] two-item array: first item - Profile level or nil, second item - Profile title or nil
|
172
|
-
def valid_profile_and_level(child, level, profile)
|
173
|
-
return LVL_PROF_DEFAULT unless child.respond_to?(:linked_profile)
|
174
|
-
|
175
|
-
validate_profile_obj(child.linked_profile, level, profile)
|
176
|
-
end
|
177
|
-
|
178
|
-
# Returns array (or array of arrays) of valid level and valid profile based on child's linked profiles
|
179
|
-
# @param obj [Object] AbideDevUtils::XCCDF::Parser::Objects::Profile objects or array of them
|
180
|
-
# @param level [String] a profile level
|
181
|
-
# @param profile [String] a partial / full profile title
|
182
|
-
# @return [Array] two-item array: first item - Profile level or nil, second item - Profile title or nil
|
183
|
-
# @return [Array] Array of two item arrays if `obj` is an array of profiles
|
184
|
-
def validate_profile_obj(obj, level, profile)
|
185
|
-
return LVL_PROF_DEFAULT if obj.nil?
|
186
|
-
return validate_profile_objs(obj, level, profile) if obj.respond_to?(:each)
|
187
|
-
|
188
|
-
validated_level = valid_level?(obj, level) ? obj.level : nil
|
189
|
-
validated_profile = valid_profile?(obj, profile) ? obj.title.to_s : nil
|
190
|
-
[validated_level, validated_profile]
|
191
|
-
end
|
192
|
-
|
193
|
-
# Returns array of arrays of valid levels and valid profiles based on all children's linked profiles
|
194
|
-
# @param objs [Array] Array of AbideDevUtils::XCCDF::Parser::Objects::Profile objects
|
195
|
-
# @param level [String] a profile level
|
196
|
-
# @param profile [String] a partial / full profile title
|
197
|
-
# @return [Array] Array of two-item arrays
|
198
|
-
def validate_profile_objs(objs, level, profile)
|
199
|
-
found = [LVL_PROF_DEFAULT]
|
200
|
-
objs.each do |obj|
|
201
|
-
validated = validate_profile_obj(obj, level, profile)
|
202
|
-
next if validated.any?(&:nil?)
|
203
|
-
|
204
|
-
found << validated
|
205
|
-
end
|
206
|
-
found
|
207
|
-
end
|
208
|
-
|
209
|
-
# Checks if a given object has a matching level. This is done
|
210
|
-
# via a basic regex match on the object's #level method
|
211
|
-
# @param obj [AbideDevUtils::XCCDF::Parser::Objects::Profile] the profile object
|
212
|
-
# @param level [String] a level to check
|
213
|
-
# @return [TrueClass] if level matches
|
214
|
-
# @return [FalseClass] if level does not match
|
215
|
-
def valid_level?(obj, level)
|
216
|
-
return true if level.nil? || obj.level.nil?
|
217
|
-
|
218
|
-
obj.level.match?(/#{Regexp.escape level}/i)
|
219
|
-
end
|
220
|
-
|
221
|
-
# Checks if a given profile object has a matching title or id. This
|
222
|
-
# is done with a regex match against the profile's title as a string
|
223
|
-
# or against it's object string representation (the ID).
|
224
|
-
# @param obj [AbideDevUtils::XCCDF::Parser::Objects::Profile] the profile object
|
225
|
-
# @param profile [String] a profile string to check
|
226
|
-
# @return [TrueClass] if `profile` matches either the profile's title or ID
|
227
|
-
# @return [FalseClass] if `profile` matches neither
|
228
|
-
def valid_profile?(obj, profile)
|
229
|
-
return true if profile.nil?
|
230
|
-
|
231
|
-
obj.title.to_s.match?(/#{Regexp.escape profile}/i) ||
|
232
|
-
obj.to_s.match?(/#{Regexp.escape profile}/i)
|
233
|
-
end
|
234
|
-
|
235
|
-
# Finds all children of the benchmark that implement AbideDevUtils::XCCDF::Parser::Objects::NumberedObject
|
236
|
-
# that are not filtered out based on optional parameters. This method recursively walks down the hierarchy
|
237
|
-
# of the benchmark to ensure that all deeply nested objects are accounted for.
|
238
|
-
# @param benchmark [AbideDevUtils::XCCDF::Parser::Objects::Benchmark] the benchmark to check
|
239
|
-
# @param only_classes [Array] An array of class names. Only children with the specified class names will be returned
|
240
|
-
# @param level [String] A profile level. Only children that have linked profiles that match this level will be returned
|
241
|
-
# @param profile [String] A profile title / id. Only children that have linked profiles that match this title / id will be returned
|
242
|
-
# @param numbered_children [Array] An array of numbered children to check. If this is empty, we start checking at the top-level of
|
243
|
-
# the benchmark. To get all of the benchmark's numbered children, this should be an empty array when calling this method.
|
244
|
-
# @return [Array] A sorted array of numbered children.
|
245
|
-
def find_all_numbered_children(benchmark, only_classes: [], level: nil, profile: nil, numbered_children: [])
|
246
|
-
benchmark.find_children_that_respond_to(:number).each do |child|
|
247
|
-
numbered_children << child if include_numbered_child?(child,
|
248
|
-
only_classes: only_classes,
|
249
|
-
level: level,
|
250
|
-
profile: profile)
|
251
|
-
find_all_numbered_children(child,
|
252
|
-
only_classes: only_classes,
|
253
|
-
level: level,
|
254
|
-
profile: profile,
|
255
|
-
numbered_children: numbered_children)
|
256
|
-
end
|
257
|
-
numbered_children.sort
|
258
|
-
end
|
259
|
-
|
260
|
-
# Returns a subset of benchmark children based on a property of that child and search values
|
261
|
-
def find_subset_of_children(children, property, search_values)
|
262
|
-
children.select { |c| search_values.include?(c.send(property)) }
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AbideDevUtils
|
4
|
-
module XCCDF
|
5
|
-
module Diff
|
6
|
-
# Holds the result of a diff on a per-item basis.
|
7
|
-
class DiffChangeResult
|
8
|
-
attr_reader :type, :old_value, :new_value
|
9
|
-
|
10
|
-
def initialize(type, old_value, new_value)
|
11
|
-
@type = type
|
12
|
-
@old_value = old_value
|
13
|
-
@new_value = new_value
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_h
|
17
|
-
{ type: type, old_value: old_value, new_value: new_value }
|
18
|
-
end
|
19
|
-
|
20
|
-
def to_a
|
21
|
-
[type, old_value, new_value]
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_s
|
25
|
-
"#{type}: #{old_value} -> #{new_value}"
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AbideDevUtils
|
4
|
-
module XCCDF
|
5
|
-
module Parser
|
6
|
-
module Objects
|
7
|
-
# Methods for providing and comparing hash digests of objects
|
8
|
-
module DigestObject
|
9
|
-
# Excludes instance variables that are not used in the digest
|
10
|
-
def exclude_from_digest(exclude)
|
11
|
-
unless exclude.is_a?(Array) || exclude.is_a?(Symbol)
|
12
|
-
raise ArgumentError, 'exclude must be an Array or Symbol'
|
13
|
-
end
|
14
|
-
|
15
|
-
@exclude_from_digest ||= []
|
16
|
-
if exclude.is_a?(Array)
|
17
|
-
exclude.map! do |e|
|
18
|
-
normalize_exclusion(e)
|
19
|
-
end
|
20
|
-
@exclude_from_digest += exclude
|
21
|
-
else
|
22
|
-
@exclude_from_digest << normalize_exclusion(exclude)
|
23
|
-
end
|
24
|
-
@exclude_from_digest.uniq!
|
25
|
-
end
|
26
|
-
|
27
|
-
# Exclusions are instance variable symbols and must be prefixed with "@"
|
28
|
-
def normalize_exclusion(exclude)
|
29
|
-
exclude = "@#{exclude}" unless exclude.to_s.start_with?('@')
|
30
|
-
exclude.to_sym
|
31
|
-
end
|
32
|
-
|
33
|
-
# Checks SHA256 digest equality
|
34
|
-
def digest_equal?(other)
|
35
|
-
digest == other.digest
|
36
|
-
end
|
37
|
-
|
38
|
-
# Returns a SHA256 digest of the object, including the digests of all
|
39
|
-
# children
|
40
|
-
def digest
|
41
|
-
return @digest if defined?(@digest)
|
42
|
-
|
43
|
-
parts = [labeled_self_digest]
|
44
|
-
children.each { |child| parts << child.digest } unless children.empty?
|
45
|
-
@digest = parts.join('|')
|
46
|
-
@digest
|
47
|
-
end
|
48
|
-
|
49
|
-
# Returns a labeled digest of the current object
|
50
|
-
def labeled_self_digest
|
51
|
-
return "#{label}:#{Digest::SHA256.hexdigest(digestable_instance_variables)}" if respond_to?(:label)
|
52
|
-
|
53
|
-
"none:#{Digest::SHA256.hexdigest(digestable_instance_variables)}"
|
54
|
-
end
|
55
|
-
|
56
|
-
# Returns a string of all instance variable values that are not nil, empty, or excluded
|
57
|
-
def digestable_instance_variables
|
58
|
-
instance_vars = instance_variables.reject { |iv| @exclude_from_digest.include?(iv) }.sort_by!(&:to_s)
|
59
|
-
return 'empty' if instance_vars.empty?
|
60
|
-
|
61
|
-
var_vals = instance_vars.map { |iv| instance_variable_get(iv) }
|
62
|
-
var_vals.reject! { |v| v.nil? || v.empty? }
|
63
|
-
return 'empty' if var_vals.empty?
|
64
|
-
|
65
|
-
var_vals.join
|
66
|
-
end
|
67
|
-
|
68
|
-
# Compares two objects by their SHA256 digests
|
69
|
-
# and returns the degree to which they are similar
|
70
|
-
# as a percentage.
|
71
|
-
def digest_similarity(other, only_labels: [], label_weights: {})
|
72
|
-
digest_parts = sorted_digest_parts(digest)
|
73
|
-
number_compared = 0
|
74
|
-
cumulative_similarity = 0.0
|
75
|
-
digest_parts.each do |digest_part|
|
76
|
-
label, self_digest = split_labeled_digest(digest_part)
|
77
|
-
next unless only_labels.empty? || only_labels.include?(label)
|
78
|
-
|
79
|
-
label_weight = label_weights.key?(label) ? label_weights[label] : 1.0
|
80
|
-
sorted_digest_parts(other.digest).each do |other_digest_part|
|
81
|
-
other_label, other_digest = split_labeled_digest(other_digest_part)
|
82
|
-
next unless (label == other_label) && (self_digest == other_digest)
|
83
|
-
|
84
|
-
number_compared += 1
|
85
|
-
cumulative_similarity += 1.0 * label_weight
|
86
|
-
break # break when found
|
87
|
-
end
|
88
|
-
end
|
89
|
-
cumulative_similarity / (number_compared.zero? ? 1.0 : number_compared)
|
90
|
-
end
|
91
|
-
|
92
|
-
def sorted_digest_parts(dgst)
|
93
|
-
@sorted_digest_parts_cache = {} unless defined?(@sorted_digest_parts_cache)
|
94
|
-
return @sorted_digest_parts_cache[dgst] if @sorted_digest_parts_cache.key?(dgst)
|
95
|
-
|
96
|
-
@sorted_digest_parts_cache ||= {}
|
97
|
-
@sorted_digest_parts_cache[dgst] = dgst.split('|').sort_by { |part| split_labeled_digest(part).first }
|
98
|
-
@sorted_digest_parts_cache[dgst]
|
99
|
-
end
|
100
|
-
|
101
|
-
# If one of the digest parts is nil and the other is not, we can't compare
|
102
|
-
def non_compatible?(digest_part, other_digest_part)
|
103
|
-
(digest_part.nil? || other_digest_part.nil?) && digest_part != other_digest_part
|
104
|
-
end
|
105
|
-
|
106
|
-
# Splits a digest into a label and digest
|
107
|
-
def split_labeled_digest(digest_part)
|
108
|
-
@labeled_digest_part_cache = {} unless defined?(@labeled_digest_part_cache)
|
109
|
-
return @labeled_digest_part_cache[digest_part] if @labeled_digest_part_cache.key?(digest_part)
|
110
|
-
|
111
|
-
@labeled_digest_part_cache[digest_part] = digest_part.split(':')
|
112
|
-
@labeled_digest_part_cache[digest_part]
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|