entitlements 0.1.7
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 +7 -0
- data/VERSION +1 -0
- data/bin/deploy-entitlements +18 -0
- data/lib/entitlements/auditor/base.rb +163 -0
- data/lib/entitlements/backend/base_controller.rb +171 -0
- data/lib/entitlements/backend/base_provider.rb +55 -0
- data/lib/entitlements/backend/dummy/controller.rb +89 -0
- data/lib/entitlements/backend/dummy.rb +3 -0
- data/lib/entitlements/backend/ldap/controller.rb +188 -0
- data/lib/entitlements/backend/ldap/provider.rb +128 -0
- data/lib/entitlements/backend/ldap.rb +4 -0
- data/lib/entitlements/backend/member_of/controller.rb +203 -0
- data/lib/entitlements/backend/member_of.rb +3 -0
- data/lib/entitlements/cli.rb +121 -0
- data/lib/entitlements/data/groups/cached.rb +120 -0
- data/lib/entitlements/data/groups/calculated/base.rb +478 -0
- data/lib/entitlements/data/groups/calculated/filters/base.rb +93 -0
- data/lib/entitlements/data/groups/calculated/filters/member_of_group.rb +32 -0
- data/lib/entitlements/data/groups/calculated/modifiers/base.rb +38 -0
- data/lib/entitlements/data/groups/calculated/modifiers/expiration.rb +56 -0
- data/lib/entitlements/data/groups/calculated/ruby.rb +137 -0
- data/lib/entitlements/data/groups/calculated/rules/base.rb +35 -0
- data/lib/entitlements/data/groups/calculated/rules/group.rb +129 -0
- data/lib/entitlements/data/groups/calculated/rules/username.rb +41 -0
- data/lib/entitlements/data/groups/calculated/text.rb +337 -0
- data/lib/entitlements/data/groups/calculated/yaml.rb +171 -0
- data/lib/entitlements/data/groups/calculated.rb +290 -0
- data/lib/entitlements/data/groups.rb +13 -0
- data/lib/entitlements/data/people/combined.rb +197 -0
- data/lib/entitlements/data/people/dummy.rb +71 -0
- data/lib/entitlements/data/people/ldap.rb +142 -0
- data/lib/entitlements/data/people/yaml.rb +102 -0
- data/lib/entitlements/data/people.rb +58 -0
- data/lib/entitlements/extras/base.rb +40 -0
- data/lib/entitlements/extras/ldap_group/base.rb +20 -0
- data/lib/entitlements/extras/ldap_group/filters/member_of_ldap_group.rb +50 -0
- data/lib/entitlements/extras/ldap_group/rules/ldap_group.rb +69 -0
- data/lib/entitlements/extras/orgchart/base.rb +32 -0
- data/lib/entitlements/extras/orgchart/logic.rb +171 -0
- data/lib/entitlements/extras/orgchart/person_methods.rb +55 -0
- data/lib/entitlements/extras/orgchart/rules/direct_report.rb +62 -0
- data/lib/entitlements/extras/orgchart/rules/management.rb +59 -0
- data/lib/entitlements/extras.rb +82 -0
- data/lib/entitlements/models/action.rb +82 -0
- data/lib/entitlements/models/group.rb +280 -0
- data/lib/entitlements/models/person.rb +149 -0
- data/lib/entitlements/plugins/dummy.rb +22 -0
- data/lib/entitlements/plugins/group_of_names.rb +28 -0
- data/lib/entitlements/plugins/posix_group.rb +46 -0
- data/lib/entitlements/plugins.rb +13 -0
- data/lib/entitlements/rule/base.rb +74 -0
- data/lib/entitlements/service/ldap.rb +405 -0
- data/lib/entitlements/util/mirror.rb +42 -0
- data/lib/entitlements/util/override.rb +64 -0
- data/lib/entitlements/util/util.rb +219 -0
- data/lib/entitlements.rb +606 -0
- metadata +343 -0
data/lib/entitlements.rb
ADDED
@@ -0,0 +1,606 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Hey there! With our use of the "contracts" module, load order is important.
|
4
|
+
|
5
|
+
# Load third party dependencies first.
|
6
|
+
require "concurrent"
|
7
|
+
require "contracts"
|
8
|
+
require "erb"
|
9
|
+
require "logger"
|
10
|
+
require "ostruct"
|
11
|
+
require "set"
|
12
|
+
require "stringio"
|
13
|
+
require "uri"
|
14
|
+
require "yaml"
|
15
|
+
|
16
|
+
# Next, pre-declare any classes that are referenced from contracts.
|
17
|
+
module Entitlements
|
18
|
+
class Auditor
|
19
|
+
class Base; end
|
20
|
+
end
|
21
|
+
class Data
|
22
|
+
class Groups
|
23
|
+
class Cached; end
|
24
|
+
class Calculated
|
25
|
+
class Base; end
|
26
|
+
class Ruby < Base; end
|
27
|
+
class Text < Base; end
|
28
|
+
class YAML < Base; end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
class People
|
32
|
+
class Combined; end
|
33
|
+
class Dummy; end
|
34
|
+
class LDAP; end
|
35
|
+
class YAML; end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
module Extras; end
|
39
|
+
class Models
|
40
|
+
class Action; end
|
41
|
+
class Group; end
|
42
|
+
class Person; end
|
43
|
+
class RuleSet
|
44
|
+
class Base; end
|
45
|
+
class Ruby < Base; end
|
46
|
+
class YAML < Base; end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
class Service
|
50
|
+
class GitHub; end
|
51
|
+
class LDAP; end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module Entitlements
|
56
|
+
include ::Contracts::Core
|
57
|
+
C = ::Contracts
|
58
|
+
|
59
|
+
IGNORED_FILES = Set.new(%w[README.md PR_TEMPLATE.md])
|
60
|
+
|
61
|
+
# Allows interpretation of ERB for the configuration file to make things less hokey.
|
62
|
+
class ERB < OpenStruct
|
63
|
+
def self.render_from_hash(template, hash)
|
64
|
+
new(hash).render(template)
|
65
|
+
end
|
66
|
+
|
67
|
+
def render(template)
|
68
|
+
::ERB.new(template, nil, "-").result(binding)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Reset all Entitlements state
|
73
|
+
#
|
74
|
+
# Takes no arguments
|
75
|
+
def self.reset!
|
76
|
+
@cache = nil
|
77
|
+
@child_classes = nil
|
78
|
+
@config = nil
|
79
|
+
@config_file = nil
|
80
|
+
@config_path_override = nil
|
81
|
+
@person_extra_methods = {}
|
82
|
+
|
83
|
+
reset_extras!
|
84
|
+
Entitlements::Data::Groups::Calculated.reset!
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.reset_extras!
|
88
|
+
extras_loaded = @extras_loaded
|
89
|
+
if extras_loaded
|
90
|
+
extras_loaded.each { |clazz| clazz.reset! if clazz.respond_to?(:reset!) }
|
91
|
+
end
|
92
|
+
@extras_loaded = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
# Set up a dummy logger.
|
96
|
+
#
|
97
|
+
# Returns a Logger.
|
98
|
+
Contract C::None => Logger
|
99
|
+
def self.dummy_logger
|
100
|
+
# :nocov:
|
101
|
+
Logger.new(StringIO.new)
|
102
|
+
# :nocov:
|
103
|
+
end
|
104
|
+
|
105
|
+
# Read the configuration file and return it as a hash.
|
106
|
+
#
|
107
|
+
# Takes no arguments.
|
108
|
+
#
|
109
|
+
# Returns a Hash.
|
110
|
+
Contract C::None => C::HashOf[String => C::Any]
|
111
|
+
def self.config
|
112
|
+
@config ||= begin
|
113
|
+
content = ERB.render_from_hash(File.read(config_file), {})
|
114
|
+
::YAML.safe_load(content)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Set the configuration directly to a Hash.
|
119
|
+
#
|
120
|
+
# config_hash - Desired value for the configuration.
|
121
|
+
#
|
122
|
+
# Returns the supplied configuration.
|
123
|
+
Contract C::HashOf[String => C::Any] => C::HashOf[String => C::Any]
|
124
|
+
def self.config=(config_hash)
|
125
|
+
@config = config_hash
|
126
|
+
end
|
127
|
+
|
128
|
+
# Determine the configuration file location. Gets the default if
|
129
|
+
# it is called before explicitly set.
|
130
|
+
#
|
131
|
+
# Returns a String.
|
132
|
+
Contract C::None => String
|
133
|
+
def self.config_file
|
134
|
+
@config_file || File.expand_path("../config/entitlements/config.yaml", File.dirname(__FILE__))
|
135
|
+
end
|
136
|
+
|
137
|
+
# Allow an alternate configuration file to be set. When this is set, it
|
138
|
+
# clears @config so it gets read upon the next invocation.
|
139
|
+
#
|
140
|
+
# path - Path to config file.
|
141
|
+
Contract String => C::Any
|
142
|
+
def self.config_file=(path)
|
143
|
+
unless File.file?(path)
|
144
|
+
raise "Specified config file = #{path.inspect} but it does not exist!"
|
145
|
+
end
|
146
|
+
|
147
|
+
@config_file = path
|
148
|
+
@config = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
# Get the configuration path for the groups. This is based on the relative
|
152
|
+
# location to the configuration file if it doesn't start with a "/".
|
153
|
+
#
|
154
|
+
# Takes no arguments.
|
155
|
+
#
|
156
|
+
# Returns a String with the config path.
|
157
|
+
def self.config_path
|
158
|
+
return @config_path_override if @config_path_override
|
159
|
+
base = config.fetch("configuration_path")
|
160
|
+
return base if base.start_with?("/")
|
161
|
+
File.expand_path(base, File.dirname(config_file))
|
162
|
+
end
|
163
|
+
|
164
|
+
# Set the configuration path for the groups. This will override the automatically
|
165
|
+
# calculated config_path that respects the algorithm noted above.
|
166
|
+
#
|
167
|
+
# path - Path to the base directory of groups.
|
168
|
+
#
|
169
|
+
# Returns the config_path that was set.
|
170
|
+
|
171
|
+
Contract String => C::Any
|
172
|
+
def self.config_path=(path)
|
173
|
+
unless path.start_with?("/")
|
174
|
+
raise ArgumentError, "Path must be absolute when setting config_path!"
|
175
|
+
end
|
176
|
+
|
177
|
+
unless File.directory?(path)
|
178
|
+
raise Errno::ENOENT, "config_path #{path.inspect} is not a directory!"
|
179
|
+
end
|
180
|
+
|
181
|
+
@config["configuration_path"] = path if @config
|
182
|
+
@config_path_override = path
|
183
|
+
end
|
184
|
+
|
185
|
+
# Keep track of backends that are registered when backends are loaded.
|
186
|
+
#
|
187
|
+
# identifier - A String with the identifier for the backend as it appears in the configuration file
|
188
|
+
# clazz - A Class reference to the backend
|
189
|
+
# priority - An Integer with the order of execution (smaller = first)
|
190
|
+
#
|
191
|
+
# Returns nothing.
|
192
|
+
Contract String, Class, Integer, C::Maybe[C::Bool] => C::Any
|
193
|
+
def self.register_backend(identifier, clazz, priority)
|
194
|
+
@backends ||= {}
|
195
|
+
@backends[identifier] = { class: clazz, priority: priority }
|
196
|
+
end
|
197
|
+
|
198
|
+
# Return the registered backends.
|
199
|
+
#
|
200
|
+
# Takes no arguments.
|
201
|
+
#
|
202
|
+
# Returns a Hash of backend identifier => class and priority.
|
203
|
+
Contract C::None => C::HashOf[String => C::HashOf[Symbol, C::Any]]
|
204
|
+
def self.backends
|
205
|
+
@backends || {}
|
206
|
+
end
|
207
|
+
|
208
|
+
# Load all extras configured by the "extras" key in the entitlements configuration.
|
209
|
+
#
|
210
|
+
# Takes no arguments.
|
211
|
+
#
|
212
|
+
# Returns nothing.
|
213
|
+
Contract C::None => nil
|
214
|
+
def self.load_extras
|
215
|
+
Entitlements.config.fetch("extras", {}).each do |extra_name, extra_cfg|
|
216
|
+
path = extra_cfg.key?("path") ? Entitlements::Util::Util.absolute_path(extra_cfg["path"]) : nil
|
217
|
+
logger.debug "Loading extra #{extra_name} (path = #{path || 'default'})"
|
218
|
+
Entitlements::Extras.load_extra(extra_name, path)
|
219
|
+
end
|
220
|
+
nil
|
221
|
+
end
|
222
|
+
|
223
|
+
# Handle a callback from Entitlements::Extras.load_extra to add a class to the tracker of loaded extra classes.
|
224
|
+
#
|
225
|
+
# clazz - Class that was loaded.
|
226
|
+
#
|
227
|
+
# Returns nothing.
|
228
|
+
Contract Class => C::Any
|
229
|
+
def self.record_loaded_extra(clazz)
|
230
|
+
@extras_loaded ||= Set.new
|
231
|
+
@extras_loaded.add(clazz)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Register all filters configured by the "filters" key in the entitlements configuration.
|
235
|
+
#
|
236
|
+
# Takes no arguments.
|
237
|
+
#
|
238
|
+
# Returns nothing.
|
239
|
+
Contract C::None => nil
|
240
|
+
def self.register_filters
|
241
|
+
Entitlements.config.fetch("filters", {}).each do |filter_name, filter_cfg|
|
242
|
+
filter_class = filter_cfg.fetch("class")
|
243
|
+
filter_clazz = Kernel.const_get(filter_class)
|
244
|
+
filter_config = filter_cfg.fetch("config", {})
|
245
|
+
|
246
|
+
logger.debug "Registering filter #{filter_name} (class: #{filter_class})"
|
247
|
+
Entitlements::Data::Groups::Calculated.register_filter(filter_name, { class: filter_clazz, config: filter_config })
|
248
|
+
end
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
|
252
|
+
@person_extra_methods = {}
|
253
|
+
|
254
|
+
# Register a method on the Entitlements::Models::Person objects. Methods are registered at
|
255
|
+
# a class level by extras. This updates @person_methods with a Hash of method_name => reference.
|
256
|
+
#
|
257
|
+
# method_name - A String with the extra method name to register.
|
258
|
+
# method_ref - A reference to the method within the appropriate class.
|
259
|
+
#
|
260
|
+
# Returns nothing.
|
261
|
+
Contract String, C::Any => C::Any
|
262
|
+
def self.register_person_extra_method(method_name, method_class_ref)
|
263
|
+
@person_extra_methods[method_name.to_sym] = method_class_ref
|
264
|
+
end
|
265
|
+
|
266
|
+
# Get the current entries in @person_methods as a hash.
|
267
|
+
#
|
268
|
+
# Takes no arguments.
|
269
|
+
#
|
270
|
+
# Returns a Hash of method_name => reference.
|
271
|
+
Contract C::None => C::HashOf[Symbol => C::Any]
|
272
|
+
def self.person_extra_methods
|
273
|
+
@person_extra_methods
|
274
|
+
end
|
275
|
+
|
276
|
+
# Return array of all registered child classes.
|
277
|
+
#
|
278
|
+
# Takes no arguments.
|
279
|
+
#
|
280
|
+
# Returns a Hash of instantiated Class objects, indexed by group name, sorted by priority.
|
281
|
+
Contract C::None => C::HashOf[C::Or[Symbol, String] => Object]
|
282
|
+
def self.child_classes
|
283
|
+
@child_classes ||= begin
|
284
|
+
backend_obj = Entitlements.config["groups"].map do |group_name, data|
|
285
|
+
[group_name, Entitlements.backends[data["type"]][:class].new(group_name)]
|
286
|
+
end.compact.to_h
|
287
|
+
|
288
|
+
# Sort first by priority, then by whether this is a mirror or not (mirrors go last), and
|
289
|
+
# finally by the length of the OU name from shortest to longest.
|
290
|
+
backend_obj.sort_by do |k, v|
|
291
|
+
[
|
292
|
+
v.priority,
|
293
|
+
Entitlements.config["groups"][k] && Entitlements.config["groups"][k].key?("mirror") ? 1 : 0,
|
294
|
+
k.length
|
295
|
+
]
|
296
|
+
end.to_h
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Method to access the configured auditors.
|
301
|
+
#
|
302
|
+
# Takes no arguments.
|
303
|
+
#
|
304
|
+
# Returns an Array of Entitlements::Auditor::* objects.
|
305
|
+
Contract C::None => C::ArrayOf[Entitlements::Auditor::Base]
|
306
|
+
def self.auditors
|
307
|
+
@auditors ||= begin
|
308
|
+
if Entitlements.config.key?("auditors")
|
309
|
+
Entitlements.config["auditors"].map do |auditor|
|
310
|
+
unless auditor.is_a?(Hash)
|
311
|
+
# :nocov:
|
312
|
+
raise ArgumentError, "Configuration error: Expected auditor to be a hash, got #{auditor.inspect}!"
|
313
|
+
# :nocov:
|
314
|
+
end
|
315
|
+
|
316
|
+
auditor_class = auditor.fetch("auditor_class")
|
317
|
+
|
318
|
+
begin
|
319
|
+
clazz = Kernel.const_get("Entitlements::Auditor::#{auditor_class}")
|
320
|
+
rescue NameError
|
321
|
+
raise ArgumentError, "Auditor class #{auditor_class.inspect} is invalid"
|
322
|
+
end
|
323
|
+
|
324
|
+
clazz.new(logger, auditor)
|
325
|
+
end
|
326
|
+
else
|
327
|
+
[]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Global logger for this run of Entitlements.
|
333
|
+
#
|
334
|
+
# Takes no arguments.
|
335
|
+
#
|
336
|
+
# Returns a Logger.
|
337
|
+
# :nocov:
|
338
|
+
def self.logger
|
339
|
+
@logger ||= dummy_logger
|
340
|
+
end
|
341
|
+
|
342
|
+
def self.set_logger(logger)
|
343
|
+
@logger = logger
|
344
|
+
end
|
345
|
+
# :nocov:
|
346
|
+
|
347
|
+
# Calculate - This runs the entitlements logic to calculate the differences, ultimately
|
348
|
+
# populating a cache and returning a list of actions. The cache and actions can then be
|
349
|
+
# consumed by `execute` to implement the changes.
|
350
|
+
#
|
351
|
+
# Takes no arguments.
|
352
|
+
#
|
353
|
+
# Returns the array of actions.
|
354
|
+
Contract C::None => C::ArrayOf[Entitlements::Models::Action]
|
355
|
+
def self.calculate
|
356
|
+
# Load extras that are configured.
|
357
|
+
Entitlements.load_extras if Entitlements.config.key?("extras")
|
358
|
+
|
359
|
+
# Pre-fetch people from configured people data sources.
|
360
|
+
Entitlements.prefetch_people
|
361
|
+
|
362
|
+
# Register filters that are configured.
|
363
|
+
Entitlements.register_filters if Entitlements.config.key?("filters")
|
364
|
+
|
365
|
+
# Keep track of the total change count.
|
366
|
+
cache[:change_count] = 0
|
367
|
+
|
368
|
+
max_parallelism = Entitlements.config["max_parallelism"] || 1
|
369
|
+
|
370
|
+
# Calculate old and new membership in each group.
|
371
|
+
thread_pool = Concurrent::FixedThreadPool.new(max_parallelism)
|
372
|
+
logger.debug("Begin prefetch and validate for all groups")
|
373
|
+
prep_start = Time.now
|
374
|
+
futures = Entitlements.child_classes.map do |group_name, obj|
|
375
|
+
Concurrent::Future.execute({ executor: thread_pool }) do
|
376
|
+
group_start = Time.now
|
377
|
+
logger.debug("Begin prefetch and validate for #{group_name}")
|
378
|
+
obj.prefetch
|
379
|
+
obj.validate
|
380
|
+
logger.debug("Finished prefetch and validate for #{group_name} in #{Time.now - group_start}")
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
futures.each(&:value!)
|
385
|
+
logger.debug("Finished all prefetch and validate in #{Time.now - prep_start}")
|
386
|
+
|
387
|
+
logger.debug("Begin all calculations")
|
388
|
+
calc_start = Time.now
|
389
|
+
actions = []
|
390
|
+
Entitlements.child_classes.map do |group_name, obj|
|
391
|
+
obj.calculate
|
392
|
+
if obj.change_count > 0
|
393
|
+
logger.debug "Group #{group_name.inspect} contributes #{obj.change_count} change(s)."
|
394
|
+
cache[:change_count] += obj.change_count
|
395
|
+
end
|
396
|
+
actions.concat(obj.actions)
|
397
|
+
end
|
398
|
+
logger.debug("Finished all calculations in #{Time.now - calc_start}")
|
399
|
+
logger.debug("Finished all prefetch, validate, and calculation in #{Time.now - prep_start}")
|
400
|
+
|
401
|
+
actions
|
402
|
+
end
|
403
|
+
|
404
|
+
# Method to execute all of the actions and run the auditors. Returns an Array of the exceptions
|
405
|
+
# raised by auditors. Any exceptions raised by providers will be raised once the auditors are
|
406
|
+
# executed.
|
407
|
+
#
|
408
|
+
# actions - An Array of Entitlements::Models::Action
|
409
|
+
#
|
410
|
+
# Returns nothing.
|
411
|
+
Contract C::KeywordArgs[
|
412
|
+
actions: C::ArrayOf[Entitlements::Models::Action]
|
413
|
+
] => nil
|
414
|
+
def self.execute(actions:)
|
415
|
+
# Set up auditors.
|
416
|
+
Entitlements.auditors.each { |auditor| auditor.setup }
|
417
|
+
|
418
|
+
# Track any raised exception to pass to the auditors.
|
419
|
+
provider_exception = nil
|
420
|
+
audit_exceptions = []
|
421
|
+
successful_actions = Set.new
|
422
|
+
|
423
|
+
# Sort the child classes by priority
|
424
|
+
begin
|
425
|
+
# Pre-apply changes for each class.
|
426
|
+
Entitlements.child_classes.each do |_, obj|
|
427
|
+
obj.preapply
|
428
|
+
end
|
429
|
+
|
430
|
+
# Apply changes from all actions.
|
431
|
+
actions.each do |action|
|
432
|
+
obj = Entitlements.child_classes.fetch(action.ou)
|
433
|
+
obj.apply(action)
|
434
|
+
successful_actions.add(action.dn)
|
435
|
+
end
|
436
|
+
rescue => e
|
437
|
+
# Populate 'provider_exception' for the auditors and then raise the exception.
|
438
|
+
provider_exception = e
|
439
|
+
raise e
|
440
|
+
ensure
|
441
|
+
# Run the audit "commit" action for each auditor. This needs to happen despite any failures that
|
442
|
+
# may occur when pre-applying or applying actions, because actions might have been applied despite
|
443
|
+
# any failures raised. Run each audit, even if one fails, and batch up the exceptions for the end.
|
444
|
+
# If there was an original exception from one of the providers, this block will be executed and then
|
445
|
+
# that original exception will be raised.
|
446
|
+
if Entitlements.auditors.any?
|
447
|
+
logger.debug "Recording data to #{Entitlements.auditors.size} audit provider(s)"
|
448
|
+
Entitlements.auditors.each do |audit|
|
449
|
+
begin
|
450
|
+
audit.commit(
|
451
|
+
actions: actions,
|
452
|
+
successful_actions: successful_actions,
|
453
|
+
provider_exception: provider_exception
|
454
|
+
)
|
455
|
+
logger.debug "Audit #{audit.description} completed successfully"
|
456
|
+
rescue => e
|
457
|
+
logger.error "Audit #{audit.description} failed: #{e.class} #{e.message}"
|
458
|
+
e.backtrace.each { |line| logger.error line }
|
459
|
+
audit_exceptions << e
|
460
|
+
end
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
# If we get here there were no provider exceptions. If there were audit exceptions raise them here.
|
466
|
+
# If there were multiple exceptions we can only raise the first one, but log a message indicating this.
|
467
|
+
return if audit_exceptions.empty?
|
468
|
+
|
469
|
+
if audit_exceptions.size > 1
|
470
|
+
logger.warn "There were #{audit_exceptions.size} audit exceptions. Only the first one is raised."
|
471
|
+
end
|
472
|
+
raise audit_exceptions.first
|
473
|
+
end
|
474
|
+
|
475
|
+
# Validate the configuration file.
|
476
|
+
#
|
477
|
+
# Takes no input.
|
478
|
+
#
|
479
|
+
# Returns nothing.
|
480
|
+
Contract C::None => nil
|
481
|
+
def self.validate_configuration_file!
|
482
|
+
# Required attributes
|
483
|
+
spec = {
|
484
|
+
"configuration_path" => { required: true, type: String },
|
485
|
+
"backends" => { required: false, type: Hash },
|
486
|
+
"people" => { required: true, type: Hash },
|
487
|
+
"people_data_source" => { required: true, type: String },
|
488
|
+
"groups" => { required: true, type: Hash },
|
489
|
+
"auditors" => { required: false, type: Array },
|
490
|
+
"filters" => { required: false, type: Hash },
|
491
|
+
"extras" => { required: false, type: Hash },
|
492
|
+
"max_parallelism" => { required: false, type: Integer },
|
493
|
+
}
|
494
|
+
|
495
|
+
Entitlements::Util::Util.validate_attr!(spec, Entitlements.config, "Entitlements configuration file")
|
496
|
+
|
497
|
+
# Make sure each group has a valid type, and then forward the validator to the child class.
|
498
|
+
# If a named backend is chosen, merge the parameters from the backend with the parameters given
|
499
|
+
# for the class configuration, and then remove all indication that a backend was used.
|
500
|
+
Entitlements.config["groups"].each do |key, data|
|
501
|
+
if data.key?("backend")
|
502
|
+
unless Entitlements.config["backends"] && Entitlements.config["backends"].key?(data["backend"])
|
503
|
+
raise "Entitlements configuration group #{key.inspect} references non-existing backend #{data['backend'].inspect}!"
|
504
|
+
end
|
505
|
+
|
506
|
+
backend = Entitlements.config["backends"].fetch(data["backend"])
|
507
|
+
unless backend.key?("type")
|
508
|
+
raise "Entitlements backend #{data['backend'].inspect} is missing a type!"
|
509
|
+
end
|
510
|
+
|
511
|
+
# Priority in the merge is given to the specific OU configured. Backend data is filled
|
512
|
+
# in only as default values when not otherwise defined.
|
513
|
+
Entitlements.config["groups"][key] = backend.merge(data)
|
514
|
+
Entitlements.config["groups"][key].delete("backend")
|
515
|
+
data = Entitlements.config["groups"][key]
|
516
|
+
end
|
517
|
+
|
518
|
+
unless data["type"].is_a?(String)
|
519
|
+
raise "Entitlements configuration group #{key.inspect} does not properly declare a type!"
|
520
|
+
end
|
521
|
+
|
522
|
+
unless Entitlements.backends.key?(data["type"])
|
523
|
+
raise "Entitlements configuration group #{key.inspect} has invalid type (#{data['type'].inspect})"
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
# Good if nothing is raised by here.
|
528
|
+
nil
|
529
|
+
end
|
530
|
+
|
531
|
+
# Method to go through each person data source and retrieve the list of people from it. Populates
|
532
|
+
# Entitlements.cache[:people][<datasource>] with the objects that can be subsequently `read` from
|
533
|
+
# with no penalty.
|
534
|
+
#
|
535
|
+
# Takes no arguments.
|
536
|
+
#
|
537
|
+
# Returns the Entitlements::Data::People::* object.
|
538
|
+
Contract C::None => C::Any
|
539
|
+
def self.prefetch_people
|
540
|
+
Entitlements.cache[:people_obj] ||= begin
|
541
|
+
people_data_sources = Entitlements.config.fetch("people", [])
|
542
|
+
if people_data_sources.empty?
|
543
|
+
raise ArgumentError, "At least one data source for people must be specified in the Entitlements configuration!"
|
544
|
+
end
|
545
|
+
|
546
|
+
# TODO: In the future, have separate data sources per group.
|
547
|
+
people_data_source_name = Entitlements.config.fetch("people_data_source", "")
|
548
|
+
if people_data_source_name.empty?
|
549
|
+
raise ArgumentError, "The Entitlements configuration must define a people_data_source!"
|
550
|
+
end
|
551
|
+
unless people_data_sources.key?(people_data_source_name)
|
552
|
+
raise ArgumentError, "The people_data_source #{people_data_source_name.inspect} is invalid!"
|
553
|
+
end
|
554
|
+
|
555
|
+
objects = people_data_sources.map do |ds_name, ds_config|
|
556
|
+
people_obj = Entitlements::Data::People.new_from_config(ds_config)
|
557
|
+
people_obj.read
|
558
|
+
[ds_name, people_obj]
|
559
|
+
end.to_h
|
560
|
+
|
561
|
+
objects.fetch(people_data_source_name)
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
# This is a global cache for the whole run of entitlements. To avoid passing objects around, since Entitlements
|
566
|
+
# by its nature is a run-once-upon-demand application.
|
567
|
+
#
|
568
|
+
# Takes no arguments.
|
569
|
+
#
|
570
|
+
# Returns a Hash that contains the cache.
|
571
|
+
#
|
572
|
+
# Note: Since this is hit a lot, to avoid the performance penalty, Contracts is not used here.
|
573
|
+
# :nocov:
|
574
|
+
def self.cache
|
575
|
+
@cache ||= {
|
576
|
+
calculated: {},
|
577
|
+
file_objects: {}
|
578
|
+
}
|
579
|
+
end
|
580
|
+
# :nocov:
|
581
|
+
end
|
582
|
+
|
583
|
+
# Finally, load everything else. Order should be unimportant here.
|
584
|
+
require_relative "entitlements/auditor/base"
|
585
|
+
require_relative "entitlements/backend/base_controller"
|
586
|
+
require_relative "entitlements/backend/base_provider"
|
587
|
+
require_relative "entitlements/backend/dummy"
|
588
|
+
require_relative "entitlements/backend/ldap"
|
589
|
+
require_relative "entitlements/backend/member_of"
|
590
|
+
require_relative "entitlements/cli"
|
591
|
+
require_relative "entitlements/data/groups"
|
592
|
+
require_relative "entitlements/data/people"
|
593
|
+
require_relative "entitlements/extras"
|
594
|
+
require_relative "entitlements/extras/base"
|
595
|
+
require_relative "entitlements/models/action"
|
596
|
+
require_relative "entitlements/models/group"
|
597
|
+
require_relative "entitlements/models/person"
|
598
|
+
require_relative "entitlements/plugins"
|
599
|
+
require_relative "entitlements/plugins/dummy"
|
600
|
+
require_relative "entitlements/plugins/group_of_names"
|
601
|
+
require_relative "entitlements/plugins/posix_group"
|
602
|
+
require_relative "entitlements/rule/base"
|
603
|
+
require_relative "entitlements/service/ldap"
|
604
|
+
require_relative "entitlements/util/mirror"
|
605
|
+
require_relative "entitlements/util/override"
|
606
|
+
require_relative "entitlements/util/util"
|