abide-data-processor 0.1.0 → 1.0.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/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 -274
- data/lib/abide-data-processor/version.rb +1 -1
- data/lib/abide-data-processor.rb +2 -0
- 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 (
|
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.0)
|
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,282 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'set'
|
5
|
-
require 'pry'
|
3
|
+
require 'abide-data-processor/parser'
|
6
4
|
|
7
5
|
module AbideDataProcessor
|
8
6
|
module Processor
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# @param control_maps: The control mappings to valid IDs
|
13
|
-
# @param module_name: The name of the module
|
14
|
-
# @param logger: The logger that we will use to log information for the user
|
15
|
-
def initialize(control_maps, logger)
|
16
|
-
@control_maps = control_maps
|
17
|
-
@logger = logger
|
18
|
-
end
|
19
|
-
|
20
|
-
# control_key_maps
|
21
|
-
# Gets all control key maps from Hiera for indexed control ID permutation searches
|
22
|
-
# @return An array of four control ID maps, each indexed by one of the four different valid permutations of a control ID
|
23
|
-
def self.control_key_maps(module_name)
|
24
|
-
key_prefix = "#{module_name}::mappings::cis"
|
25
|
-
%w[hiera_title hiera_title_num number title].each_with_object([]) do |key, ary|
|
26
|
-
ary << [key_prefix, key].join('::')
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def cis_hiera_key(prefix, key)
|
31
|
-
"#{prefix}::#{key}"
|
32
|
-
end
|
33
|
-
|
34
|
-
# create_resources
|
35
|
-
# @param resources_hash: the hash of controls to be enforces, user will provide this
|
36
|
-
# @param only: the list of controls to be enforce only, pulled from cis.pp
|
37
|
-
# @param ignore: the list of controls to be ignore, pulled from cis.pp
|
38
|
-
# @param control_configs: the custom control configurations pulled from cis.pp
|
39
|
-
# Return a hash to be convert to Puppet code.
|
40
|
-
def create_resources(resources_hash, only, ignore, control_configs)
|
41
|
-
resources = real_resources(resources_hash, 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
|
-
|
93
|
-
# Only and ignore list check
|
94
|
-
next unless only_and_ignore_check(name, name_map, only, ignore)
|
95
|
-
|
96
|
-
# Control dependent check
|
97
|
-
if params.key?('dependent')
|
98
|
-
unless dependent_check(all_controls_name, params['dependent'], ignore, only)
|
99
|
-
# Below is just a sure fire way to make sure that we will never use the resource
|
100
|
-
only.delete(name) # Remove from the only list
|
101
|
-
ignore.add(name) # Add the name of the current control to the ignore list if we're not gonna enforce it
|
102
|
-
@logger.inform("Control #{name} will not be enforced because the controls that it depends on is invalid.")
|
103
|
-
next
|
104
|
-
end
|
105
|
-
end
|
106
|
-
# Find if there are any custom control configs from the cis.pp based on the control's name and its permutation
|
107
|
-
customized = find_control_customization(name, name_map[name], control_configs)
|
108
|
-
params.deep_merge!(customized, merge_hash_arrays: true)
|
109
|
-
control_params.deep_merge!(params, merge_hash_arrays: true)
|
110
|
-
end
|
111
|
-
control_params
|
112
|
-
end
|
113
|
-
|
114
|
-
# dependent_check
|
115
|
-
# @param all_controls_name: Set of all controls name that parsed from the hiera data
|
116
|
-
# @param all_dependent_resources: An array of all the resources that is relied upon
|
117
|
-
# @param ignore: List of controls to be ignore
|
118
|
-
# @param only: List of only controls that need to be enforce
|
119
|
-
# return true if a dependent control is
|
120
|
-
def dependent_check(all_controls_name, all_dependent_resources, ignore, only)
|
121
|
-
all_dependent_resources.each do |resource_name|
|
122
|
-
valid_resource_name = map_for_control_name(resource_name, @control_maps)
|
123
|
-
# Bounce immediately if it is not a part of the controls we're enforcing
|
124
|
-
return false unless filter_function(resource_name, valid_resource_name, all_controls_name)
|
125
|
-
|
126
|
-
if !ignore.empty?
|
127
|
-
return false if filter_function(resource_name, valid_resource_name, ignore)
|
128
|
-
elsif !only.empty?
|
129
|
-
return false unless filter_function(resource_name, valid_resource_name, only)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# order_resources
|
135
|
-
# A work in progress to integrate more metaparamenters in
|
136
|
-
# Checks for and creates resources based off of `before`, `after`, `notify`, `require` parameters
|
137
|
-
# specified in a controls hash
|
138
|
-
# @param resources Output of real_resources()
|
139
|
-
# @return An array of resource hashes indexed in the order their contents should be created
|
140
|
-
def order_resources(resources)
|
141
|
-
before = {}
|
142
|
-
after = {}
|
143
|
-
req = {}
|
144
|
-
notify = {}
|
145
|
-
resources.each do |_, data|
|
146
|
-
create_ordered_resource!('before', before, data)
|
147
|
-
create_ordered_resource!('notify', notify, data)
|
148
|
-
create_ordered_resource!('after', after, data)
|
149
|
-
create_ordered_resource!('require', req, data)
|
150
|
-
end
|
151
|
-
|
152
|
-
before.deep_merge!(notify)
|
153
|
-
after.deep_merge!(req)
|
154
|
-
|
155
|
-
[before, resources, after]
|
156
|
-
end
|
157
|
-
|
158
|
-
# filter_function
|
159
|
-
# A general function to see if a control name is in a supply list of control name
|
160
|
-
# @param name: The name of the control that we have
|
161
|
-
# @param name_map: All valid control ID permutation of the param name
|
162
|
-
# @set_of_control: Either the ignore or the only list to go through
|
163
|
-
# return true if control ID is found in set_of_control
|
164
|
-
def filter_function(name, name_map, set_of_control)
|
165
|
-
name_list = name_map[name]
|
166
|
-
return true if set_of_control.include?(name)
|
167
|
-
|
168
|
-
name_list.each do |n|
|
169
|
-
return true if set_of_control.include?(n)
|
170
|
-
end
|
171
|
-
|
172
|
-
false
|
173
|
-
end
|
174
|
-
|
175
|
-
# only_and_ignore_check
|
176
|
-
# @param name: name of the control to check if it's in either only or ignore list
|
177
|
-
# @param name_map: the name map of valid ID permutation for the `name` param
|
178
|
-
# @param only: the list of controls that will get enforced only
|
179
|
-
# @param ignore: the list of controls that will be ignored
|
180
|
-
# @return false when control is either not in the only list or is in the ignore list.
|
181
|
-
# else return true
|
182
|
-
def only_and_ignore_check(name, name_map, only, ignore)
|
183
|
-
if !only.empty? && !filter_function(name, name_map, only)
|
184
|
-
@logger.inform("Control #{name} will be skipped because it is not in the only list.")
|
185
|
-
return false
|
186
|
-
end
|
187
|
-
|
188
|
-
if !ignore.empty? && filter_function(name, name_map, ignore)
|
189
|
-
@logger.inform("Control #{name} will be skipped because it is in the ignore list.")
|
190
|
-
return false
|
191
|
-
end
|
192
|
-
true
|
193
|
-
end
|
194
|
-
|
195
|
-
# create_ordered_resource!
|
196
|
-
# Creates a resource hash from a resource declaration found in a `before`, `after`, `require`, or `notify` parameter
|
197
|
-
# @param order_key Either 'before', 'after', `notify`, or `require`
|
198
|
-
# @param container Either the before hash, the notify hash, the require hash or the after hash.
|
199
|
-
# The container is modified in place.
|
200
|
-
# @param res_data Resource data from the real_resources hash
|
201
|
-
def create_ordered_resource!(order_key, container, res_data)
|
202
|
-
res_data.each do |_, data|
|
203
|
-
next unless data.key?(order_key)
|
204
|
-
|
205
|
-
data[order_key].each do |title, params|
|
206
|
-
container[params['type']] = {} unless container.key?(params['type'])
|
207
|
-
container[params['type']][title] = params.reject { |k, _| k == 'type' }
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
# mutate_ordering_params!
|
213
|
-
# This takes the Hiera resource declarations in a `before` or `after` param and transforms
|
214
|
-
# them into and array of Puppet resource references. Puppet resource references take the
|
215
|
-
# form: Resource::Type['<resource title'].
|
216
|
-
# @param resources Output from real_resources()
|
217
|
-
def mutate_ordering_params!(resources)
|
218
|
-
resources.each do |res_type, res_data|
|
219
|
-
%w[before notify after require].each do |order_key|
|
220
|
-
res_data.each do |res_title, params|
|
221
|
-
next unless params.key?(order_key)
|
222
|
-
|
223
|
-
references = []
|
224
|
-
params[order_key].each do |k, v|
|
225
|
-
references << resource_reference(v['type'], k)
|
226
|
-
end
|
227
|
-
resources[res_type][res_title][order_key] = references
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
# resource_reference
|
234
|
-
# Returns a Puppet resource reference string
|
235
|
-
# @param res_type A Puppet resource type
|
236
|
-
# @param title A Puppet resource title
|
237
|
-
def resource_reference(res_type, title)
|
238
|
-
type_ref = res_type.split('::').map(&:capitalize).join('::')
|
239
|
-
"#{type_ref}[#{title}]"
|
240
|
-
end
|
241
|
-
|
242
|
-
# find_control_customization
|
243
|
-
# Finds any control parameter customizations passed in via control_configs
|
244
|
-
# @param name The control name (or other valid permutation)
|
245
|
-
# @param control_configs The $control_configs parameter from cis.pp
|
246
|
-
# @param name_map: The array of valid permutation of name
|
247
|
-
# @return A hash of customized parameters. If none were found, nil
|
248
|
-
def find_control_customization(name, name_map, control_configs)
|
249
|
-
return {} if control_configs.empty?
|
250
|
-
|
251
|
-
mapped_key(control_configs, name, name_map)
|
252
|
-
end
|
253
|
-
|
254
|
-
# map_for_control_name
|
255
|
-
# Returns a hash of all valid permutations of the given control id
|
256
|
-
# @param name The control name or other valid permutation
|
257
|
-
# @param maps All maps
|
258
|
-
def map_for_control_name(name, maps)
|
259
|
-
maps.each do |map|
|
260
|
-
return map if map&.fetch(name, false) # returns the map if fetch returns false
|
261
|
-
end
|
262
|
-
nil
|
263
|
-
end
|
264
|
-
|
265
|
-
# mapped_key
|
266
|
-
# Finds an item in the given hash that matches one of the values in name_map
|
267
|
-
# @param hsh The hash to search i.e the custom config hash
|
268
|
-
# @param name The name of the current control that we're looking to see if it has any custom configs
|
269
|
-
# @param name_map An array name for valid control permutations
|
270
|
-
# @return The value from the hash if found, or nil
|
271
|
-
def mapped_key(hsh, name, name_map)
|
272
|
-
return hsh[name] if hsh&.fetch(name, false)
|
273
|
-
|
274
|
-
name_map.each do |pkey|
|
275
|
-
return hsh[pkey] if hsh&.fetch(pkey, false)
|
276
|
-
end
|
277
|
-
nil
|
278
|
-
end
|
279
|
-
|
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)
|
280
10
|
end
|
281
11
|
end
|
282
12
|
end
|
data/lib/abide-data-processor.rb
CHANGED
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:
|
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
|