abide-data-processor 0.3.0 → 1.0.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 +11 -25
- data/abide-data-processor.gemspec +4 -2
- data/lib/abide-data-processor/parser.rb +722 -0
- data/lib/abide-data-processor/processor.rb +4 -275
- data/lib/abide-data-processor/version.rb +1 -1
- metadata +22 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0c06067b1e8e9d9fd97a99a4a006fbe5f194103783b086d7be46053468a0f7e
|
4
|
+
data.tar.gz: 10b23ed882b6ae68e057c2bde2c31ee7d46b5bee3e28b11223b96d1342666bc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1774914d0700a4afdaf9c9f4dc7d92da3811ef9e37a271b1b517d9a37920f7d7c2fd778302c369daab51c28c25f7ac51ddf92cf530ba31190ac06b06017d410
|
7
|
+
data.tar.gz: 9f7fcffebc49eb7590b36d2b38caf7fe1126de54414c2267f2b4544928c4c165c862ac345058a08f15fade7a74133c0e232943c0c4ac00e0b8fff6fbb1ee9a07
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
abide-data-processor (0.
|
5
|
-
|
4
|
+
abide-data-processor (1.0.0)
|
5
|
+
deep_merge (~> 1.2)
|
6
|
+
rgl (~> 0.5)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
@@ -39,11 +40,8 @@ GEM
|
|
39
40
|
concurrent-ruby (1.1.9)
|
40
41
|
console (1.13.1)
|
41
42
|
fiber-local
|
42
|
-
deep_merge (1.2.
|
43
|
+
deep_merge (1.2.2)
|
43
44
|
diff-lcs (1.4.4)
|
44
|
-
facter (4.2.5)
|
45
|
-
hocon (~> 1.3)
|
46
|
-
thor (>= 1.0.1, < 2.0)
|
47
45
|
faraday (1.8.0)
|
48
46
|
faraday-em_http (~> 1.0)
|
49
47
|
faraday-em_synchrony (~> 1.0)
|
@@ -68,6 +66,7 @@ GEM
|
|
68
66
|
fast_gettext (1.8.0)
|
69
67
|
fiber-local (1.0.0)
|
70
68
|
gem-release (2.2.2)
|
69
|
+
generator (0.0.1)
|
71
70
|
github_changelog_generator (1.16.4)
|
72
71
|
activesupport
|
73
72
|
async (>= 1.25.0)
|
@@ -77,11 +76,9 @@ GEM
|
|
77
76
|
octokit (~> 4.6)
|
78
77
|
rainbow (>= 2.2.1)
|
79
78
|
rake (>= 10.0)
|
80
|
-
hiera (3.7.0)
|
81
|
-
hocon (1.3.1)
|
82
79
|
i18n (1.8.10)
|
83
80
|
concurrent-ruby (~> 1.0)
|
84
|
-
|
81
|
+
lazy_priority_queue (0.1.1)
|
85
82
|
method_source (1.0.0)
|
86
83
|
minitest (5.14.4)
|
87
84
|
multi_json (1.15.0)
|
@@ -107,23 +104,13 @@ GEM
|
|
107
104
|
byebug (~> 11.0)
|
108
105
|
pry (~> 0.10)
|
109
106
|
public_suffix (4.0.6)
|
110
|
-
puppet (7.12.1)
|
111
|
-
concurrent-ruby (~> 1.0)
|
112
|
-
deep_merge (~> 1.0)
|
113
|
-
facter (> 2.0.1, < 5)
|
114
|
-
fast_gettext (~> 1.1)
|
115
|
-
hiera (>= 3.2.1, < 4)
|
116
|
-
locale (~> 2.1)
|
117
|
-
multi_json (~> 1.10)
|
118
|
-
puppet-resource_api (~> 1.5)
|
119
|
-
scanf (~> 1.0)
|
120
|
-
semantic_puppet (~> 1.0)
|
121
|
-
puppet-resource_api (1.8.14)
|
122
|
-
hocon (>= 1.0)
|
123
107
|
rainbow (3.0.0)
|
124
108
|
rake (12.3.3)
|
125
109
|
regexp_parser (2.1.1)
|
126
110
|
rexml (3.2.5)
|
111
|
+
rgl (0.5.7)
|
112
|
+
lazy_priority_queue (~> 0.1.0)
|
113
|
+
stream (~> 0.5.3)
|
127
114
|
rspec (3.10.0)
|
128
115
|
rspec-core (~> 3.10.0)
|
129
116
|
rspec-expectations (~> 3.10.0)
|
@@ -160,9 +147,8 @@ GEM
|
|
160
147
|
sawyer (0.8.2)
|
161
148
|
addressable (>= 2.3.5)
|
162
149
|
faraday (> 0.8, < 2.0)
|
163
|
-
|
164
|
-
|
165
|
-
thor (1.1.0)
|
150
|
+
stream (0.5.3)
|
151
|
+
generator
|
166
152
|
timers (4.3.3)
|
167
153
|
tzinfo (2.0.4)
|
168
154
|
concurrent-ruby (~> 1.0)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'lib/abide-data-processor/version'
|
2
4
|
|
3
5
|
Gem::Specification.new do |spec|
|
@@ -28,8 +30,8 @@ Gem::Specification.new do |spec|
|
|
28
30
|
spec.require_paths = ["lib"]
|
29
31
|
|
30
32
|
# Prod dependencies
|
31
|
-
|
32
|
-
spec.add_dependency '
|
33
|
+
spec.add_dependency 'deep_merge', '~> 1.2'
|
34
|
+
spec.add_dependency 'rgl', '~> 0.5'
|
33
35
|
|
34
36
|
# Dev dependencies
|
35
37
|
spec.add_development_dependency 'bundler'
|
@@ -0,0 +1,722 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'deep_merge'
|
4
|
+
require 'rgl/adjacency'
|
5
|
+
require 'rgl/topsort'
|
6
|
+
require 'rgl/traversal'
|
7
|
+
require 'set'
|
8
|
+
|
9
|
+
module AbideDataProcessor
|
10
|
+
# This module contains the logic for creating resource data from Hiera data.
|
11
|
+
module Parser
|
12
|
+
MAP_TYPES = %w[hiera_title hiera_title_num number title].freeze
|
13
|
+
METAPARAMS = %w[dependent before require subscribe notify].freeze
|
14
|
+
|
15
|
+
# Parse Hiera data into a resource data Hash
|
16
|
+
# @param hiera_data [Hash] Hiera data to parse
|
17
|
+
# @param control_maps [Array] Control maps to use
|
18
|
+
# @return [Hash] Parsed resource data
|
19
|
+
def self.parse(hiera_data, control_maps, control_configs: {}, ignore: [], only: [])
|
20
|
+
ResourceDataParser.new(
|
21
|
+
hiera_data,
|
22
|
+
control_maps,
|
23
|
+
control_configs: control_configs,
|
24
|
+
ignore: ignore,
|
25
|
+
only: only
|
26
|
+
).parse
|
27
|
+
end
|
28
|
+
|
29
|
+
# This module handles data validation for the CIS data parser
|
30
|
+
module Validation
|
31
|
+
# Validates the hiera_data parameter and either raises an ArgumentError or returns the hiera_data parameter.
|
32
|
+
# @param hiera_data [Hash] The Hiera data to be parsed.
|
33
|
+
# @return [Hash] The Hiera data to be parsed.
|
34
|
+
# @raise [ArgumentError] If the hiera_data parameter is not a non-empty Hash.
|
35
|
+
def validate_hiera_data(hiera_data)
|
36
|
+
unless not_nil_or_empty?(hiera_data) && hiera_data.is_a?(Hash)
|
37
|
+
raise ArgumentError, 'hiera_data must be a non-nil, non-empty Hash'
|
38
|
+
end
|
39
|
+
|
40
|
+
hiera_data
|
41
|
+
end
|
42
|
+
|
43
|
+
# Validates the control_maps parameter and either raises an ArgumentError or returns the control_maps parameter.
|
44
|
+
# @param control_maps [Array] The control maps to be parsed.
|
45
|
+
# @return [Array] The control maps to be parsed.
|
46
|
+
# @raise [ArgumentError] If the control_maps parameter is not a non-empty Array of Hashes.
|
47
|
+
def validate_control_maps(control_maps)
|
48
|
+
unless not_nil_or_empty?(control_maps) && array_of_hashes?(control_maps)
|
49
|
+
raise ArgumentError, 'control_maps must be a non-nil, non-empty Array of Hashes'
|
50
|
+
end
|
51
|
+
|
52
|
+
control_maps
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks if the value is not nil or empty.
|
56
|
+
# @param value [Any] The value to be checked.
|
57
|
+
# @return [Boolean] True if the value is not nil or empty, false otherwise.
|
58
|
+
def not_nil_or_empty?(value)
|
59
|
+
!value.nil? && !value.empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Checks if the value is an Array of Hashes.
|
63
|
+
# @param value [Any] The value to be checked.
|
64
|
+
# @return [Boolean] True if the value is an Array of Hashes, false otherwise.
|
65
|
+
def array_of_hashes?(value)
|
66
|
+
value.is_a?(Array) && value.all? { |h| h.is_a?(Hash) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Parser class for resource Hiera data.
|
71
|
+
# rubocop:disable Metrics/ClassLength
|
72
|
+
class ResourceDataParser
|
73
|
+
include Validation
|
74
|
+
attr_reader :hiera_data, :control_maps, :resources
|
75
|
+
|
76
|
+
def initialize(hiera_data, control_maps, control_configs: {}, ignore: [], only: [])
|
77
|
+
@hiera_data = validate_hiera_data(hiera_data)
|
78
|
+
@control_maps = validate_control_maps(control_maps)
|
79
|
+
@control_configs = control_configs
|
80
|
+
@ignore = ignore
|
81
|
+
@only = only
|
82
|
+
@resources = RGL::DirectedAdjacencyGraph.new
|
83
|
+
@controls = Set.new
|
84
|
+
@filtered = Set.new
|
85
|
+
@dependent = {}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Parse the Hiera data into a Hash used by Puppet to create the resources.
|
89
|
+
# The way this works is by first creating a DAG and adding all resources to the graph
|
90
|
+
# as vertices, with an edge for each resource pointing from a dummy node, :root, to the
|
91
|
+
# resource. We then add edges to the graph based on the `before_me` and `after_me` lists
|
92
|
+
# of each resource and remove the :root-connected edges for each resource that has a
|
93
|
+
# `before_me` list, and remove the :root-connected edges for each resource in a `after_me`
|
94
|
+
# list. Finally, we sort the graph into an Array populated with a single Hash of ordered
|
95
|
+
# resources and return that Hash.
|
96
|
+
# @return [Array] A sorted array of resource hashes.
|
97
|
+
# rubocop:disable Metrics/MethodLength
|
98
|
+
def parse
|
99
|
+
@hiera_data.each do |name, data|
|
100
|
+
resource = AbideDataProcessor::Parser.new_resource(name, data, @control_maps)
|
101
|
+
add_control_names(resource)
|
102
|
+
add_dependent_mapping(resource) # Map any controls this resource depends on
|
103
|
+
@resources.add_vertex(resource) # Add all the resources to the graph
|
104
|
+
@resources.add_edge(:root, resource) # Establish the root -> resource edges
|
105
|
+
add_edge_ordering(resource) # Add resource ordering edges
|
106
|
+
end
|
107
|
+
# If the resource should be filtered (i.e. only or ignore), remove it from the graph.
|
108
|
+
filter_resources!
|
109
|
+
# Verify that all dependent resources are in the graph, remove them if not.
|
110
|
+
remove_unsatisfied_dependents!
|
111
|
+
# Sort the graph and return the array of ordered resource hashes
|
112
|
+
sort_resources.map do |r|
|
113
|
+
r.add_control_configs(@control_configs)
|
114
|
+
resource_data(r)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# rubocop:enable Metrics/MethodLength
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# Adds control neames for the given resource to the @controls set.
|
122
|
+
# @param resource [Resource] The resource to add control names for.
|
123
|
+
def add_control_names(resource)
|
124
|
+
return unless resource.controls
|
125
|
+
|
126
|
+
@controls.merge(resource.control_names).flatten!
|
127
|
+
@controls.merge(resource.mapped_control_names).flatten!
|
128
|
+
end
|
129
|
+
|
130
|
+
# Calls the given Resource's `resource_data` method, filters out any resource references
|
131
|
+
# in metaparameters that references filtered resources, and returns the result.
|
132
|
+
# @param resource [Resource] The resource to be filtered.
|
133
|
+
# @return [Hash] The filtered resource data.
|
134
|
+
# rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
135
|
+
def resource_data(resource)
|
136
|
+
data = resource.resource_data.dup
|
137
|
+
data.each do |_, res_data|
|
138
|
+
res_data.each do |_, params|
|
139
|
+
METAPARAMS.each do |param|
|
140
|
+
next unless params.key?(param)
|
141
|
+
|
142
|
+
params[param].reject! { |r| @filtered.to_a.map(&:resource_reference).include?(r) }
|
143
|
+
params.delete(param) if params[param].empty?
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
data
|
148
|
+
end
|
149
|
+
# rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity
|
150
|
+
|
151
|
+
# Removes Resources from the graph if they should be filtered.
|
152
|
+
def filter_resources!
|
153
|
+
@resources.depth_first_search do |resource|
|
154
|
+
next if resource == :root
|
155
|
+
|
156
|
+
if filter_resource?(resource)
|
157
|
+
@resources.remove_vertex(resource) # Remove resource's graph vertex
|
158
|
+
@filtered.add(resource) # Add resource to filtered set
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Checks whether the resource should be filtered out based on the ignore and only lists.
|
164
|
+
# @param resource [Resource] The resource to check.
|
165
|
+
# @return [Boolean] True if the resource should be filtered out, false otherwise.
|
166
|
+
def filter_resource?(resource)
|
167
|
+
return true if control_in?(resource, @ignore)
|
168
|
+
return true unless @only.empty? || control_in?(resource, @only)
|
169
|
+
|
170
|
+
false
|
171
|
+
end
|
172
|
+
|
173
|
+
# Adds a mapping for a dependent control and the resources that depend on it.
|
174
|
+
# @param resource [Resource] The resource to add the mapping for.
|
175
|
+
def add_dependent_mapping(resource)
|
176
|
+
return unless resource.dependent
|
177
|
+
|
178
|
+
resource.dependent.each do |control_name|
|
179
|
+
@dependent[control_name] = [] unless @dependent.key?(control_name)
|
180
|
+
@dependent[control_name] << resource
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Checks the dependent controls against all controls after filtered resource controls are removed
|
185
|
+
# and removes any dependent resources that are not satisfied.
|
186
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
187
|
+
def remove_unsatisfied_dependents!
|
188
|
+
dependent_set = Set.new(@dependent.keys)
|
189
|
+
filtered_set = Set.new(@filtered.to_a.map(&:control_names)).flatten
|
190
|
+
filtered_mapped = Set.new(@filtered.to_a.map(&:mapped_control_names)).flatten
|
191
|
+
|
192
|
+
all_controls = @controls.subtract(filtered_set + filtered_mapped)
|
193
|
+
return if dependent_set.proper_subset?(all_controls) # All dependent controls exist in the graph
|
194
|
+
|
195
|
+
(dependent_set - all_controls).each do |control_name|
|
196
|
+
@dependent[control_name].each do |resource|
|
197
|
+
@resources.remove_vertex(resource)
|
198
|
+
@filtered.add(resource)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
203
|
+
|
204
|
+
# Gets all verticies in the graph that have the associated control
|
205
|
+
# @param control_name [String] The name of the control to check.
|
206
|
+
# @return [Array] The verticies that have the associated control.
|
207
|
+
def collect_verticies_by_control(control_name)
|
208
|
+
@resources.vertices.select { |r| r.control?(control_name) }
|
209
|
+
end
|
210
|
+
|
211
|
+
# Checks if the given Resource has a control in the given list.
|
212
|
+
# @param resource [Resource] The resource to check.
|
213
|
+
# @param control_list [Array] The list of controls to check against.
|
214
|
+
# @return [Boolean] True if the resource is in the control list, false otherwise.
|
215
|
+
def control_in?(resource, control_list)
|
216
|
+
return false if control_list.empty?
|
217
|
+
|
218
|
+
control_list.each do |ignored_control|
|
219
|
+
return true if resource.control?(ignored_control)
|
220
|
+
end
|
221
|
+
false
|
222
|
+
end
|
223
|
+
|
224
|
+
# Adds edges to the graph based on the given Resource's `before_me` and `after_me` lists.
|
225
|
+
# @param resource [Resource] The Resource to add edges for.
|
226
|
+
def add_edge_ordering(resource)
|
227
|
+
add_before_me_edge(resource)
|
228
|
+
add_after_me_edge(resource)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Adds edges to the graph based on the given Resource's `before_me` list.
|
232
|
+
# @param resource [Resource] The Resource to add edges for.
|
233
|
+
def add_before_me_edge(resource)
|
234
|
+
resource.before_me.flatten.each do |before|
|
235
|
+
next unless before # Skip if this `before` is nil, empty, or falsy (e.g. false, 0, etc.)
|
236
|
+
next if before.equal?(resource) # Skip if this `before` is the same as the current resource
|
237
|
+
|
238
|
+
# We remove the edge from root to this resource if it exists because this resource is no longer
|
239
|
+
# attached to the root of the graph as it has other resources before it.
|
240
|
+
@resources.remove_edge(:root, resource) if @resources.has_edge?(:root, resource)
|
241
|
+
# Add the edge from the before resource to this resource
|
242
|
+
@resources.add_edge(before, resource) unless @resources.has_edge?(before, resource)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Adds edges to the graph based on the given Resource's `after_me` list.
|
247
|
+
# @param resource [Resource] The Resource to add edges for.
|
248
|
+
def add_after_me_edge(resource)
|
249
|
+
resource.after_me.flatten.each do |after|
|
250
|
+
next unless after # Skip if this `after` is nil, empty, or falsy (e.g. false, 0, etc.)
|
251
|
+
next if after.equal?(resource) # Skip if this `after` is the same as the current resource
|
252
|
+
|
253
|
+
# We remove the edge from root to the `after` resource if it exists because the `after` resource
|
254
|
+
# is no longer attached to the root of the graph as this resources comes before it.
|
255
|
+
@resources.remove_edge(:root, after) if @resources.has_edge?(:root, after)
|
256
|
+
# Add the edge from this resource to the after resource
|
257
|
+
@resources.add_edge(resource, after) unless @resources.has_edge?(resource, after)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# This method validates that the resources graph has no cycles and then returns a topological sort of the graph
|
262
|
+
# as an Array of Resource objects.
|
263
|
+
# @return [Array] The sorted Resources.
|
264
|
+
# @raise [ArgumentError] If the resources graph has any cycles.
|
265
|
+
def sort_resources
|
266
|
+
raise "Resource cyclic ordering detected: #{@resources.cycles}" unless @resources.acyclic?
|
267
|
+
|
268
|
+
# We call topsort on the graph to get the sorted list of resources, convert it to an array, and
|
269
|
+
# remove the root node.
|
270
|
+
@resources.topsort_iterator.to_a.flatten.uniq.reject { |r| r == :root }
|
271
|
+
end
|
272
|
+
end
|
273
|
+
# rubocop:enable Metrics/ClassLength
|
274
|
+
|
275
|
+
# This class holds all base attributes and methods for every syntax object.
|
276
|
+
# rubocop:disable Metrics/ClassLength
|
277
|
+
class ProcessorObject
|
278
|
+
include Validation
|
279
|
+
attr_reader :name, *METAPARAMS.map(&:to_sym)
|
280
|
+
|
281
|
+
def initialize(name, data, control_maps)
|
282
|
+
@name = name
|
283
|
+
@data = validate_hiera_data(data)
|
284
|
+
@control_maps = validate_control_maps(control_maps)
|
285
|
+
@dependent = Set.new
|
286
|
+
initialize_metaparams(@data, @control_maps)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Determines if the name supplied is equal to the name of the object.
|
290
|
+
# This is overridden by subclasses to implement name mapped matches.
|
291
|
+
# @param name [String] The name to be compared to the object's name.
|
292
|
+
# @return [Boolean] True if the name is equal to the object's name, false otherwise.
|
293
|
+
def name?(_name)
|
294
|
+
raise NotImplementedError, 'This method must be implemented by a subclass'
|
295
|
+
end
|
296
|
+
|
297
|
+
# Abstract method to be implemented by subclasses.
|
298
|
+
# Returns a representation of this object as a Hash usable by Puppet's
|
299
|
+
# create_resources function.
|
300
|
+
def resource_data
|
301
|
+
raise NotImplementedError, 'This method must be implemented by a subclass'
|
302
|
+
end
|
303
|
+
|
304
|
+
# Returns any Resource objects that must be ordered before this object.
|
305
|
+
# @return [Array] The Resources that must be ordered before this object.
|
306
|
+
def before_me
|
307
|
+
defined?(@before_me) ? @before_me : initialize_before_me
|
308
|
+
end
|
309
|
+
|
310
|
+
# Returns any Resource objects that must be ordered after this object.
|
311
|
+
# @return [Array] The Resources that must be ordered after this object.
|
312
|
+
def after_me
|
313
|
+
defined?(@after_me) ? @after_me : initialize_after_me
|
314
|
+
end
|
315
|
+
|
316
|
+
# Converts this object to a String.
|
317
|
+
# @return [String] The class and name of this object.
|
318
|
+
def to_s
|
319
|
+
"#{self.class.name}('#{@name}')"
|
320
|
+
end
|
321
|
+
|
322
|
+
# Gives a more detailed String representation of this object.
|
323
|
+
# @return [String] The class, object id, and name of this object.
|
324
|
+
def inspect
|
325
|
+
"#<#{self.class.name}:#{object_id} '#{@name}'>"
|
326
|
+
end
|
327
|
+
|
328
|
+
private
|
329
|
+
|
330
|
+
# This method normalizes an array of Resources, or anything really, by
|
331
|
+
# flattening it, removing any nil values, removing any duplicates, and
|
332
|
+
# rejecting any empty objects if they respond to `empty?`. It then
|
333
|
+
# returns the new array.
|
334
|
+
# @param resources [Array] The array of Resources to be normalized.
|
335
|
+
# @return [Array] The normalized array of Resources.
|
336
|
+
def normalize_resource_array(array)
|
337
|
+
array.flatten.compact.uniq.reject { |r| r.empty? if r.respond_to?(:empty?) }
|
338
|
+
end
|
339
|
+
|
340
|
+
# This method normalizes an array of Resources, or anything really, by
|
341
|
+
# flattening it, removing any nil values, removing any duplicates, and
|
342
|
+
# rejecting any empty objects if they respond to `empty?`. It does this
|
343
|
+
# in place, directly modifying the input array.
|
344
|
+
# @param resources [Array] The array of Resources to be normalized.
|
345
|
+
def normalize_resource_array!(array)
|
346
|
+
array.flatten!
|
347
|
+
array.compact!
|
348
|
+
array.uniq!
|
349
|
+
array.reject! { |r| r.empty? if r.respond_to?(:empty?) }
|
350
|
+
end
|
351
|
+
|
352
|
+
# Initializes any relevant metaparameters based on the data supplied.
|
353
|
+
# @param data [Hash] The resource data to be parsed.
|
354
|
+
# @param control_maps [Array] The control maps to be used.
|
355
|
+
def initialize_metaparams(data, control_maps)
|
356
|
+
METAPARAMS.each do |param|
|
357
|
+
value, bool_value = parse_metaparam(data[param], control_maps)
|
358
|
+
raw_value, raw_bool_value = parse_raw_metaparam(data, param)
|
359
|
+
set_metaparam_instance_vars(param, value, raw_value)
|
360
|
+
define_metaparam_bool_methods(param, raw_bool_value, bool_value)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Initilizes the before_me instance variable with a list of Resources
|
365
|
+
# that must be ordered before this object.
|
366
|
+
# @return [Array] The list of Resources that must be ordered before this object.
|
367
|
+
def initialize_before_me
|
368
|
+
ctrls = @controls ? calculate_ordered_controls('before_me') : []
|
369
|
+
this = calculate_self_ordering('require', 'subscribe')
|
370
|
+
@before_me = normalize_resource_array(this.concat(ctrls))
|
371
|
+
@before_me
|
372
|
+
end
|
373
|
+
|
374
|
+
# Initializes the after_me instance variable with a list of Resources
|
375
|
+
# that must be ordered after this object.
|
376
|
+
# @return [Array] The list of Resources that must be ordered after this object.
|
377
|
+
def initialize_after_me
|
378
|
+
ctrls = @controls ? calculate_ordered_controls('after_me') : []
|
379
|
+
this = calculate_self_ordering('notify', 'subscribe')
|
380
|
+
@after_me = normalize_resource_array(this.concat(ctrls))
|
381
|
+
@after_me
|
382
|
+
end
|
383
|
+
|
384
|
+
# This method adds the supplied Resource to the inverse ordering list of this
|
385
|
+
# object based on the supplied metaparameter. This method is never directly used
|
386
|
+
# by an object on itself, rather it is called by other objects when they establish
|
387
|
+
# ordering relationships with this object. Because this method is private, other
|
388
|
+
# objects must use `send` to call it.
|
389
|
+
# @param metaparam [String] The metaparameter to inverse
|
390
|
+
# @param resource [Resource] The Resource to be added to the inverse ordering list.
|
391
|
+
def add_inverse_ordered_resource(metaparam, resource)
|
392
|
+
if %w[require subscribe].include?(metaparam)
|
393
|
+
add_after_me(resource)
|
394
|
+
elsif %w[before notify].include?(metaparam)
|
395
|
+
add_before_me(resource)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# This method calculates the ordering of this object based on the
|
400
|
+
# ordering of the controls that are defined for this object.
|
401
|
+
# @param order_function [String] The function to use to calculate the ordering (before_me or after_me).
|
402
|
+
# @return [Array] The list of Resources gathered from the order function return of all controls.
|
403
|
+
def calculate_ordered_controls(order_function)
|
404
|
+
@controls.each_with_object([]) do |control, ary|
|
405
|
+
ary << control.send(order_function.to_sym)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# This method calculates the ordering of this object based on the
|
410
|
+
# the supplied metaparameters. This function is used with "like pairs"
|
411
|
+
# of metaparameters, such as "require" and "subscribe".
|
412
|
+
# @param metaparameters [Array] The metaparameters to use to calculate the ordering.
|
413
|
+
# @return [Array] The list of Resources gathered from this object's metaparameters.
|
414
|
+
def calculate_self_ordering(*metaparams)
|
415
|
+
ordered = metaparams.each_with_object([]) do |mparam, ary|
|
416
|
+
next unless send("#{mparam}?".to_sym)
|
417
|
+
|
418
|
+
ordered_resources = send(mparam.to_sym)
|
419
|
+
ordered_resources.each { |r| r.send(:add_inverse_ordered_resource, mparam, self) }
|
420
|
+
|
421
|
+
ary << ordered_resources
|
422
|
+
end
|
423
|
+
normalize_resource_array(ordered)
|
424
|
+
end
|
425
|
+
|
426
|
+
# Returns appropriate values for instance variables of the given metaparam based off the supplied value.
|
427
|
+
# @param value [Array] The metaparameter declaration value from Hiera.
|
428
|
+
# @param control_maps [Array] The relevant control maps used in Resource creation.
|
429
|
+
# @return [Array] Values for the instance variables of the given metaparam. The order of the values
|
430
|
+
# is: Resource collection value, boolean value.
|
431
|
+
def parse_metaparam(value, control_maps)
|
432
|
+
return [nil, false] unless not_nil_or_empty?(value)
|
433
|
+
|
434
|
+
return parse_dependent_param(value) if value.is_a?(Array)
|
435
|
+
|
436
|
+
objects = value.each_with_object([]) do |(k, v), a|
|
437
|
+
a << AbideDataProcessor::Parser.new_resource(k, v, control_maps)
|
438
|
+
end
|
439
|
+
[normalize_resource_array(objects), !objects.empty?]
|
440
|
+
end
|
441
|
+
|
442
|
+
# Adds a each dependent control from a list of dependent controls to the
|
443
|
+
# @dependent instance variable.
|
444
|
+
# @param value [Array] The dependent controls to be added to the @dependent instance variable.
|
445
|
+
def parse_dependent_param(value)
|
446
|
+
value.each { |x| @dependent.add(x) }
|
447
|
+
end
|
448
|
+
|
449
|
+
# Returns appropriate raw value for instance variables of the given metaparam based off the supplied value.
|
450
|
+
# The raw value is the text values for the metaparameter declaration supplied via the resource data.
|
451
|
+
# @param data [Hash] The resource data to be parsed.
|
452
|
+
# @param param [String] The metaparameter to be parsed.
|
453
|
+
# @return [Array] Values for the instance variables of the given metaparam. The order of the values
|
454
|
+
# is: raw value, boolean raw value.
|
455
|
+
def parse_raw_metaparam(data, param)
|
456
|
+
raw_value = data.fetch(param, nil)
|
457
|
+
[raw_value, (!raw_value.nil? && !raw_value.empty?)]
|
458
|
+
end
|
459
|
+
|
460
|
+
# Sets the instance variables of the given metaparam based off the supplied values.
|
461
|
+
# @param param [String] The metaparameter to be set.
|
462
|
+
# @param value [Array] The Resource value to be set.
|
463
|
+
# @param raw_value [Array] The raw value to be set.
|
464
|
+
def set_metaparam_instance_vars(param, value, raw_value)
|
465
|
+
instance_variable_set("@#{param}", value)
|
466
|
+
instance_variable_set("@#{param}_raw", raw_value)
|
467
|
+
end
|
468
|
+
|
469
|
+
# Defines singleton methods for this instance of ProcessorObject that are used to determine
|
470
|
+
# if the metaparameter is set.
|
471
|
+
# @param param [String] The metaparameter that will have boolean methods defined.
|
472
|
+
# @param raw_value [Boolean] The boolean value for the <metaparam>_raw? method.
|
473
|
+
# @param value [Boolean] The boolean value for the <metaparam>? method.
|
474
|
+
def define_metaparam_bool_methods(param, raw_value, value)
|
475
|
+
define_singleton_method("#{param}_raw?".to_sym) { raw_value }
|
476
|
+
define_singleton_method("#{param}?".to_sym) { value }
|
477
|
+
end
|
478
|
+
|
479
|
+
# Returns the mapped names for the given control identifier.
|
480
|
+
# @param identifier [String] The control identifier to be mapped.
|
481
|
+
# @return [Array] The mapped names for the given control identifier.
|
482
|
+
def find_mapped_names(identifier)
|
483
|
+
@control_maps.each do |control_map|
|
484
|
+
return control_map[identifier] if control_map.include?(identifier)
|
485
|
+
end
|
486
|
+
[]
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# This class represents a single control in the data structure.
|
491
|
+
class Control < ProcessorObject
|
492
|
+
attr_reader :mapped_names, :params, :param_names, :resource_params
|
493
|
+
|
494
|
+
def initialize(name, data, control_maps)
|
495
|
+
super(name, data, control_maps)
|
496
|
+
@mapped_names = find_mapped_names(@name)
|
497
|
+
@params = @data
|
498
|
+
@resource_params = @data.reject { |k, _v| METAPARAMS.include?(k) }
|
499
|
+
@param_names = Set.new(@params.keys)
|
500
|
+
end
|
501
|
+
|
502
|
+
def name?(name)
|
503
|
+
@name == name || @mapped_names.include?(name)
|
504
|
+
end
|
505
|
+
|
506
|
+
def param?(param_name)
|
507
|
+
@param_names.include?(param_name)
|
508
|
+
end
|
509
|
+
|
510
|
+
def param(param_name)
|
511
|
+
@params[param_name]
|
512
|
+
end
|
513
|
+
|
514
|
+
def resource_data
|
515
|
+
@resource_params
|
516
|
+
end
|
517
|
+
end
|
518
|
+
# rubocop:enable Metrics/ClassLength
|
519
|
+
|
520
|
+
# This class represents a single Puppet resource (class, defined type, etc.)
|
521
|
+
class Resource < ProcessorObject
|
522
|
+
attr_reader :name, :type, :controls, :control_names, :mapped_control_names
|
523
|
+
|
524
|
+
def initialize(name, data, control_maps)
|
525
|
+
super(name, data, control_maps)
|
526
|
+
@type = @data['type']
|
527
|
+
@controls = create_control_classes(@data['controls'])
|
528
|
+
@control_names = Set.new(@controls.map(&:name)).flatten
|
529
|
+
@mapped_control_names = Set.new(@controls.map(&:mapped_names).flatten).flatten
|
530
|
+
initialize_control_metaparams
|
531
|
+
end
|
532
|
+
|
533
|
+
# Adds overriding parameter values to controls in this resource
|
534
|
+
# if this resource has a matching control.
|
535
|
+
# @param data [Hash] The resource data to be parsed.
|
536
|
+
def add_control_configs(control_configs)
|
537
|
+
control_configs.each do |control, configs|
|
538
|
+
next unless control?(control)
|
539
|
+
|
540
|
+
@controls.each do |control_class|
|
541
|
+
next unless control_class.name?(control)
|
542
|
+
|
543
|
+
control_class.resource_params.deep_merge!(configs)
|
544
|
+
end
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
# Outputs a representation of this object as a Hash usable by Puppet's
|
549
|
+
# create_resources function.
|
550
|
+
def resource_data
|
551
|
+
control_params = control_parameters
|
552
|
+
METAPARAMS.each do |mparam|
|
553
|
+
next if mparam == 'dependent'
|
554
|
+
|
555
|
+
refs = resource_references(mparam, control_params)
|
556
|
+
next if refs.nil?
|
557
|
+
|
558
|
+
control_params[mparam] = refs
|
559
|
+
end
|
560
|
+
{ @type => { @name => control_params } }
|
561
|
+
end
|
562
|
+
|
563
|
+
# This method returns a string representation of this Resource in the resource reference
|
564
|
+
# format used by Puppet.
|
565
|
+
# @return [String] A string representation of this Resource in the resource reference format.
|
566
|
+
def resource_reference
|
567
|
+
type_ref = @type.split('::').map(&:capitalize).join('::')
|
568
|
+
"#{type_ref}['#{@name}']"
|
569
|
+
end
|
570
|
+
|
571
|
+
# This method checks if this Resource contains the given control.
|
572
|
+
# @param control [String] The control to be checked.
|
573
|
+
# @return [Boolean] True if this Resource contains the given control, false otherwise.
|
574
|
+
def control?(control_name)
|
575
|
+
@control_names.include?(control_name) || @mapped_control_names.include?(control_name)
|
576
|
+
end
|
577
|
+
|
578
|
+
# This method checks if this Resource contains the given parameter.
|
579
|
+
# @param param_name [String] The parameter to be checked.
|
580
|
+
# @return [Boolean] True if this Resource contains the given parameter, false otherwise.
|
581
|
+
def param?(param_name)
|
582
|
+
if param_name.respond_to?(:each)
|
583
|
+
param_name.all? { |name| param?(name) }
|
584
|
+
else
|
585
|
+
@param_names.include?(param_name)
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
private
|
590
|
+
|
591
|
+
# This method gathers the resource data for each control this Resource contains.
|
592
|
+
# @return [Hash] The resource data for each control this Resource contains.
|
593
|
+
def control_parameters
|
594
|
+
@controls.each_with_object({}) do |control, h|
|
595
|
+
h.deep_merge(control.resource_data)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
# This method gets the resource references for the given metaparameter and control parameters.
|
600
|
+
# @param mparam [String] The metaparameter to be checked.
|
601
|
+
# @param control_params [Hash] The control parameters to be checked.
|
602
|
+
# @return [Array] The resource references for the given metaparameter and control parameters.
|
603
|
+
# @return [nil] If the given metaparameter is not set on this Resource.
|
604
|
+
def resource_references(mparam, control_params)
|
605
|
+
# rubocop:disable Style/RedundantSelf
|
606
|
+
# we use self here because `require` is a metaparam and we don't want to
|
607
|
+
# call `Kernel#require` accidentally.
|
608
|
+
this_mparam = self.send(mparam.to_sym)
|
609
|
+
# rubocop:enable Style/RedundantSelf
|
610
|
+
return if this_mparam.nil? || this_mparam.compact.empty?
|
611
|
+
|
612
|
+
if control_params.key?(mparam)
|
613
|
+
control_params[mparam].concat(this_mparam.map(&:resource_reference))
|
614
|
+
else
|
615
|
+
this_mparam.map(&:resource_reference)
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
# Adds a Resource to the before_me list.
|
620
|
+
# @param resource [Resource] The Resource to be added to the before_me list.
|
621
|
+
def add_before_me(resource)
|
622
|
+
before_me.append(resource)
|
623
|
+
end
|
624
|
+
|
625
|
+
# Adds a Resource to the after_me list.
|
626
|
+
# @param resource [Resource] The Resource to be added to the after_me list.
|
627
|
+
def add_after_me(resource)
|
628
|
+
after_me.append(resource)
|
629
|
+
end
|
630
|
+
|
631
|
+
# Initializes all metaparameter values of all controls that this Resource contains
|
632
|
+
# and brings those values into the scope of this Resource. Also initializes the
|
633
|
+
# @before_me and @after_me instance variables.
|
634
|
+
def initialize_control_metaparams
|
635
|
+
METAPARAMS.each { |mparam| initialize_control_metaparameter(mparam) }
|
636
|
+
@before_me = initialize_before_me
|
637
|
+
@after_me = initialize_after_me
|
638
|
+
end
|
639
|
+
|
640
|
+
# Initializes a single supplied metaparameter for all controls that this Resource
|
641
|
+
# contains and brings those values into the scope of this Resource.
|
642
|
+
# @param mparam [String] The metaparameter to be initialized.
|
643
|
+
def initialize_control_metaparameter(mparam)
|
644
|
+
ctrl_objects = @controls.map { |c| c.send(mparam.to_sym) }.flatten
|
645
|
+
return if ctrl_objects.empty?
|
646
|
+
|
647
|
+
current_objects = instance_variable_get("@#{mparam}") || []
|
648
|
+
all_meta_objects = ctrl_objects.concat(current_objects)
|
649
|
+
instance_variable_set("@#{mparam}", all_meta_objects.flatten.compact.uniq)
|
650
|
+
define_singleton_method("#{mparam}?".to_sym) { all_meta_objects.any? }
|
651
|
+
end
|
652
|
+
|
653
|
+
# Creates a new Control class for each control in the data structure if that Control class
|
654
|
+
# does not already exist in the cache.
|
655
|
+
# @param control_data [Array] The control data of the resource from the Hiera data.
|
656
|
+
def create_control_classes(control_data)
|
657
|
+
control_data.map { |cname, cdata| AbideDataProcessor::Parser.new_control(cname, cdata, @control_maps) }
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
class << self
|
662
|
+
# Creates a new Resource object. If an object with the same resource name, resource data, and control maps
|
663
|
+
# already exists, it will be returned instead of creating a new one.
|
664
|
+
# @param resource_name [String] The name of the resource.
|
665
|
+
# @param resource_data [Hash] The data for the resource.
|
666
|
+
# @param control_maps [Array] The control maps for the resource.
|
667
|
+
# @return [Resource] The new or cached Resource object.
|
668
|
+
def new_resource(resource_name, resource_data, control_maps)
|
669
|
+
cache_key = [Resource.name, resource_name, resource_data, control_maps]
|
670
|
+
cached = cache_get(cache_key)
|
671
|
+
return cached unless cached.nil?
|
672
|
+
|
673
|
+
new_resource = Resource.new(resource_name, resource_data, control_maps)
|
674
|
+
cache_add(cache_key, new_resource)
|
675
|
+
new_resource
|
676
|
+
end
|
677
|
+
|
678
|
+
# Creates a new Control object. If an object with the same control name, control data, and control maps
|
679
|
+
# already exists, it will be returned instead of creating a new one.
|
680
|
+
# @param control_name [String] The name of the control.
|
681
|
+
# @param control_data [Hash] The data for the control.
|
682
|
+
# @param control_maps [Array] The control maps for the control.
|
683
|
+
# @return [Control] The new or cached Control object.
|
684
|
+
def new_control(control_name, control_data, control_maps)
|
685
|
+
cache_key = [Control.name, control_name, control_data, control_maps]
|
686
|
+
cached = cache_get(cache_key)
|
687
|
+
return cached unless cached.nil?
|
688
|
+
|
689
|
+
new_control = Control.new(control_name, control_data, control_maps)
|
690
|
+
cache_add(cache_key, new_control)
|
691
|
+
new_control
|
692
|
+
end
|
693
|
+
|
694
|
+
# Clears the current cache. Used in testing.
|
695
|
+
def clear_cache
|
696
|
+
@object_cache = {}
|
697
|
+
end
|
698
|
+
|
699
|
+
private
|
700
|
+
|
701
|
+
# Helper method to add a ProcessorObject (or subclass of one) to the cache.
|
702
|
+
# @param key [Array] The key for the cache. An array comprised of the object name, object data,
|
703
|
+
# and control maps.
|
704
|
+
# @param resource [ProcessorObject] The object to add to the cache.
|
705
|
+
def cache_add(key, object)
|
706
|
+
object_cache[key] = object
|
707
|
+
end
|
708
|
+
|
709
|
+
# Helper method to retrieve a ProcessorObject from the cache.
|
710
|
+
# @param key [Array] The key for the cache. An array comprised of the resource name, resource data, and control maps.
|
711
|
+
# @return [ProcessorObject] The object from the cache, or nil if the object doesn't exist.
|
712
|
+
def cache_get(key)
|
713
|
+
object_cache.fetch(key, nil)
|
714
|
+
end
|
715
|
+
|
716
|
+
# Holds the object cache. If the object cache doesn't exist, it will be created.
|
717
|
+
def object_cache
|
718
|
+
@object_cache ||= {}
|
719
|
+
end
|
720
|
+
end
|
721
|
+
end
|
722
|
+
end
|
@@ -1,283 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'set'
|
3
|
+
require 'abide-data-processor/parser'
|
5
4
|
|
6
5
|
module AbideDataProcessor
|
7
6
|
module Processor
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
# @param control_maps: The control mappings to valid IDs
|
12
|
-
# @param module_name: The name of the module
|
13
|
-
# @param logger: The logger that we will use to log information for the user
|
14
|
-
def initialize(control_maps, logger)
|
15
|
-
@control_maps = control_maps
|
16
|
-
@logger = logger
|
17
|
-
end
|
18
|
-
|
19
|
-
# control_key_maps
|
20
|
-
# Gets all control key maps from Hiera for indexed control ID permutation searches
|
21
|
-
# @return An array of four control ID maps, each indexed by one of the four different valid permutations of a control ID
|
22
|
-
def self.control_key_maps(module_name)
|
23
|
-
key_prefix = "#{module_name}::mappings::cis"
|
24
|
-
%w[hiera_title hiera_title_num number title].each_with_object([]) do |key, ary|
|
25
|
-
ary << [key_prefix, key].join('::')
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def cis_hiera_key(prefix, key)
|
30
|
-
"#{prefix}::#{key}"
|
31
|
-
end
|
32
|
-
|
33
|
-
# create_resources
|
34
|
-
# @param resources_hash: the hash of controls to be enforces, user will provide this
|
35
|
-
# @param only: the list of controls to be enforce only, pulled from cis.pp
|
36
|
-
# @param ignore: the list of controls to be ignore, pulled from cis.pp
|
37
|
-
# @param control_configs: the custom control configurations pulled from cis.pp
|
38
|
-
# Return a hash to be convert to Puppet code.
|
39
|
-
def create_resources(resources_hash, only, ignore, control_configs)
|
40
|
-
unfreezed_resources = Marshal.load(Marshal.dump(resources_hash))
|
41
|
-
resources = real_resources(unfreezed_resources, only.to_set, ignore.to_set, control_configs)
|
42
|
-
ordered_resources = order_resources(resources)
|
43
|
-
|
44
|
-
mutate_ordering_params!(ordered_resources[1])
|
45
|
-
ordered_resources
|
46
|
-
end
|
47
|
-
|
48
|
-
# Everything else except the create_resource function will be private
|
49
|
-
private
|
50
|
-
|
51
|
-
# real_resources
|
52
|
-
# Formats a Hiera resources hash into a hash used by order_resources()
|
53
|
-
# @param resources_hash The raw resources hash pulled from hiera
|
54
|
-
# @param only The $only parameter from cis.pp
|
55
|
-
# @param ignore The $ignore parameter from cis.pp
|
56
|
-
# @param control_configs The $control_configs parameter from cis.pp
|
57
|
-
def real_resources(resources_hash, only, ignore, control_configs)
|
58
|
-
real_resources = {}
|
59
|
-
all_controls_name = Set.new # subject to change
|
60
|
-
|
61
|
-
# Grabbing all the control name here into a set
|
62
|
-
resources_hash.each do |_title, data|
|
63
|
-
all_controls_name |= data['controls'].keys.to_set
|
64
|
-
end
|
65
|
-
|
66
|
-
resources_hash.each do |title, data|
|
67
|
-
resource_params = if data.key?('controls')
|
68
|
-
extract_control_params(data['controls'], only, ignore, control_configs, all_controls_name)
|
69
|
-
else
|
70
|
-
{}
|
71
|
-
end
|
72
|
-
if real_resources.key?(data['type']) && real_resources[data['type']].key?(title)
|
73
|
-
real_resources[data['type']][title].deep_merge!(resource_params, merge_hash_arrays: true)
|
74
|
-
else
|
75
|
-
real_resources[data['type']] = { title => resource_params }
|
76
|
-
end
|
77
|
-
end
|
78
|
-
real_resources
|
79
|
-
end
|
80
|
-
|
81
|
-
# extract_control_params
|
82
|
-
# Extracts resource parameters from a Hiera resource hash item's `controls` key
|
83
|
-
# @param control_data The resource hash item's `controls` key-value pair (i.e. data['controls'])
|
84
|
-
# @param only The $only parameter from cis.pp
|
85
|
-
# @param ignore The $ignore parameter from cis.pp
|
86
|
-
# @param control_configs The $control_configs parameter from cis.pp
|
87
|
-
# @param all_controls_name: All of the controls name
|
88
|
-
def extract_control_params(control_data, only, ignore, control_configs, all_controls_name)
|
89
|
-
control_params = {}
|
90
|
-
control_data.each do |name, params|
|
91
|
-
name_map = map_for_control_name(name, @control_maps)
|
92
|
-
next if name_map.nil?
|
93
|
-
# Only and ignore list check
|
94
|
-
# The name_map that got passed in here is a hash
|
95
|
-
next unless only_and_ignore_check(name, name_map, only, ignore)
|
96
|
-
|
97
|
-
# Control dependent check
|
98
|
-
if params.key?('dependent')
|
99
|
-
unless dependent_check(all_controls_name, params['dependent'], ignore, only)
|
100
|
-
# Below is just a sure fire way to make sure that we will never use the resource
|
101
|
-
only.delete(name) # Remove from the only list
|
102
|
-
ignore.add(name) # Add the name of the current control to the ignore list if we're not gonna enforce it
|
103
|
-
@logger.debug("Control #{name} will not be enforced because the controls that it depends on is invalid.")
|
104
|
-
next
|
105
|
-
end
|
106
|
-
end
|
107
|
-
# Find if there are any custom control configs from the cis.pp based on the control's name and its permutation
|
108
|
-
customized = find_control_customization(name, name_map[name], control_configs) # Check for failuer here
|
109
|
-
params.deep_merge!(customized, merge_hash_arrays: true)
|
110
|
-
control_params.deep_merge!(params, merge_hash_arrays: true)
|
111
|
-
end
|
112
|
-
control_params
|
113
|
-
end
|
114
|
-
|
115
|
-
# dependent_check
|
116
|
-
# @param all_controls_name: Set of all controls name that parsed from the hiera data
|
117
|
-
# @param all_dependent_resources: An array of all the resources that is relied upon
|
118
|
-
# @param ignore: List of controls to be ignore
|
119
|
-
# @param only: List of only controls that need to be enforce
|
120
|
-
# return true if a dependent control is
|
121
|
-
def dependent_check(all_controls_name, all_dependent_resources, ignore, only)
|
122
|
-
all_dependent_resources.each do |resource_name|
|
123
|
-
valid_resource_name = map_for_control_name(resource_name, @control_maps)
|
124
|
-
# Bounce immediately if it is not a part of the controls we're enforcing
|
125
|
-
return false unless filter_function(resource_name, valid_resource_name, all_controls_name)
|
126
|
-
|
127
|
-
if !ignore.empty?
|
128
|
-
return false if filter_function(resource_name, valid_resource_name, ignore)
|
129
|
-
elsif !only.empty?
|
130
|
-
return false unless filter_function(resource_name, valid_resource_name, only)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# order_resources
|
136
|
-
# A work in progress to integrate more metaparamenters in
|
137
|
-
# Checks for and creates resources based off of `before`, `after`, `notify`, `require` parameters
|
138
|
-
# specified in a controls hash
|
139
|
-
# @param resources Output of real_resources()
|
140
|
-
# @return An array of resource hashes indexed in the order their contents should be created
|
141
|
-
def order_resources(resources)
|
142
|
-
before = {}
|
143
|
-
after = {}
|
144
|
-
req = {}
|
145
|
-
notify = {}
|
146
|
-
resources.each do |_, data|
|
147
|
-
create_ordered_resource!('before', before, data)
|
148
|
-
create_ordered_resource!('notify', notify, data)
|
149
|
-
create_ordered_resource!('after', after, data)
|
150
|
-
create_ordered_resource!('require', req, data)
|
151
|
-
end
|
152
|
-
|
153
|
-
before.deep_merge!(notify)
|
154
|
-
after.deep_merge!(req)
|
155
|
-
|
156
|
-
[before, resources, after]
|
157
|
-
end
|
158
|
-
|
159
|
-
# filter_function
|
160
|
-
# A general function to see if a control name is in a supply list of control name
|
161
|
-
# @param name: The name of the control that we have
|
162
|
-
# @param name_map: Hash that contains all valid control ID permutation of the param name
|
163
|
-
# @set_of_control: Either the ignore or the only list to go through
|
164
|
-
# return true if control ID is found in set_of_control
|
165
|
-
def filter_function(name, name_map, set_of_control)
|
166
|
-
name_list = name_map[name] # Grab the array that contain all valid permutation of the ID
|
167
|
-
return true if set_of_control.include?(name)
|
168
|
-
|
169
|
-
name_list.each do |n|
|
170
|
-
return true if set_of_control.include?(n)
|
171
|
-
end
|
172
|
-
|
173
|
-
false
|
174
|
-
end
|
175
|
-
|
176
|
-
# only_and_ignore_check
|
177
|
-
# @param name: name of the control to check if it's in either only or ignore list
|
178
|
-
# @param name_map: a hash of the name map of valid ID permutation for the `name` param
|
179
|
-
# @param only: the list of controls that will get enforced only
|
180
|
-
# @param ignore: the list of controls that will be ignored
|
181
|
-
# @return false when control is either not in the only list or is in the ignore list.
|
182
|
-
# else return true
|
183
|
-
def only_and_ignore_check(name, name_map, only, ignore)
|
184
|
-
if !only.empty? && !filter_function(name, name_map, only)
|
185
|
-
@logger.debug("Control #{name} will be skipped because it is not in the only list.")
|
186
|
-
return false
|
187
|
-
end
|
188
|
-
|
189
|
-
if !ignore.empty? && filter_function(name, name_map, ignore)
|
190
|
-
@logger.debug("Control #{name} will be skipped because it is in the ignore list.")
|
191
|
-
return false
|
192
|
-
end
|
193
|
-
true
|
194
|
-
end
|
195
|
-
|
196
|
-
# create_ordered_resource!
|
197
|
-
# Creates a resource hash from a resource declaration found in a `before`, `after`, `require`, or `notify` parameter
|
198
|
-
# @param order_key Either 'before', 'after', `notify`, or `require`
|
199
|
-
# @param container Either the before hash, the notify hash, the require hash or the after hash.
|
200
|
-
# The container is modified in place.
|
201
|
-
# @param res_data Resource data from the real_resources hash
|
202
|
-
def create_ordered_resource!(order_key, container, res_data)
|
203
|
-
res_data.each do |_, data|
|
204
|
-
next unless data.key?(order_key)
|
205
|
-
|
206
|
-
data[order_key].each do |title, params|
|
207
|
-
container[params['type']] = {} unless container.key?(params['type'])
|
208
|
-
container[params['type']][title] = params.reject { |k, _| k == 'type' }
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
# mutate_ordering_params!
|
214
|
-
# This takes the Hiera resource declarations in a `before` or `after` param and transforms
|
215
|
-
# them into and array of Puppet resource references. Puppet resource references take the
|
216
|
-
# form: Resource::Type['<resource title'].
|
217
|
-
# @param resources Output from real_resources()
|
218
|
-
def mutate_ordering_params!(resources)
|
219
|
-
resources.each do |res_type, res_data|
|
220
|
-
%w[before notify after require].each do |order_key|
|
221
|
-
res_data.each do |res_title, params|
|
222
|
-
next unless params.key?(order_key)
|
223
|
-
|
224
|
-
references = []
|
225
|
-
params[order_key].each do |k, v|
|
226
|
-
references << resource_reference(v['type'], k)
|
227
|
-
end
|
228
|
-
resources[res_type][res_title][order_key] = references
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
# resource_reference
|
235
|
-
# Returns a Puppet resource reference string
|
236
|
-
# @param res_type A Puppet resource type
|
237
|
-
# @param title A Puppet resource title
|
238
|
-
def resource_reference(res_type, title)
|
239
|
-
type_ref = res_type.split('::').map(&:capitalize).join('::')
|
240
|
-
"#{type_ref}[#{title}]"
|
241
|
-
end
|
242
|
-
|
243
|
-
# find_control_customization
|
244
|
-
# Finds any control parameter customizations passed in via control_configs
|
245
|
-
# @param name The control name (or other valid permutation)
|
246
|
-
# @param control_configs The $control_configs parameter from cis.pp
|
247
|
-
# @param name_map: The array of valid permutation of name
|
248
|
-
# @return A hash of customized parameters. If none were found, nil
|
249
|
-
def find_control_customization(name, name_map, control_configs)
|
250
|
-
return {} if control_configs.empty?
|
251
|
-
|
252
|
-
mapped_key(control_configs, name, name_map)
|
253
|
-
end
|
254
|
-
|
255
|
-
# map_for_control_name
|
256
|
-
# Returns a hash of all valid permutations of the given control id
|
257
|
-
# @param name The control name or other valid permutation
|
258
|
-
# @param maps All maps
|
259
|
-
def map_for_control_name(name, maps)
|
260
|
-
maps.each do |map|
|
261
|
-
return map if map&.fetch(name, false) # returns the map if fetch returns false
|
262
|
-
end
|
263
|
-
nil
|
264
|
-
end
|
265
|
-
|
266
|
-
# mapped_key
|
267
|
-
# Finds an item in the given hash that matches one of the values in name_map
|
268
|
-
# @param hsh The hash to search i.e the custom config hash
|
269
|
-
# @param name The name of the current control that we're looking to see if it has any custom configs
|
270
|
-
# @param name_map An array name for valid control permutations
|
271
|
-
# @return The value from the hash if found, or nil
|
272
|
-
def mapped_key(hsh, name, name_map)
|
273
|
-
return hsh[name] if hsh&.fetch(name, false)
|
274
|
-
|
275
|
-
name_map.each do |pkey|
|
276
|
-
return hsh[pkey] if hsh&.fetch(pkey, false)
|
277
|
-
end
|
278
|
-
nil
|
279
|
-
end
|
280
|
-
|
7
|
+
def self.create_resources(resources_hash, control_maps, only, ignore, control_configs)
|
8
|
+
unfrozen_resources = Marshal.load(Marshal.dump(resources_hash))
|
9
|
+
AbideDataProcessor::Parser.parse(unfrozen_resources, control_maps, only, ignore, control_configs)
|
281
10
|
end
|
282
11
|
end
|
283
12
|
end
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abide-data-processor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- abide-team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: deep_merge
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '1.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rgl
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.5'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
40
|
+
version: '0.5'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: bundler
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -241,6 +255,7 @@ files:
|
|
241
255
|
- bin/setup
|
242
256
|
- lib/abide-data-processor.rb
|
243
257
|
- lib/abide-data-processor/logger.rb
|
258
|
+
- lib/abide-data-processor/parser.rb
|
244
259
|
- lib/abide-data-processor/processor.rb
|
245
260
|
- lib/abide-data-processor/version.rb
|
246
261
|
homepage: https://github.com/puppetlabs/abide-data-processor
|