conjur-policy-parser 0.12.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 +7 -0
- data/.gitignore +9 -0
- data/.project +18 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ci/test.sh +6 -0
- data/conjur-policy-parser.gemspec +31 -0
- data/jenkins.sh +27 -0
- data/lib/conjur-policy-parser-version.rb +7 -0
- data/lib/conjur-policy-parser.rb +32 -0
- data/lib/conjur/policy/doc.rb +43 -0
- data/lib/conjur/policy/invalid.rb +12 -0
- data/lib/conjur/policy/logger.rb +12 -0
- data/lib/conjur/policy/resolver.rb +262 -0
- data/lib/conjur/policy/types/base.rb +417 -0
- data/lib/conjur/policy/types/create.rb +40 -0
- data/lib/conjur/policy/types/deny.rb +33 -0
- data/lib/conjur/policy/types/give.rb +28 -0
- data/lib/conjur/policy/types/grant.rb +72 -0
- data/lib/conjur/policy/types/include.rb +46 -0
- data/lib/conjur/policy/types/member.rb +37 -0
- data/lib/conjur/policy/types/permit.rb +59 -0
- data/lib/conjur/policy/types/policy.rb +180 -0
- data/lib/conjur/policy/types/records.rb +518 -0
- data/lib/conjur/policy/types/retire.rb +36 -0
- data/lib/conjur/policy/types/revoke.rb +32 -0
- data/lib/conjur/policy/types/update.rb +36 -0
- data/lib/conjur/policy/yaml/handler.rb +392 -0
- data/lib/conjur/policy/yaml/loader.rb +60 -0
- metadata +205 -0
@@ -0,0 +1,417 @@
|
|
1
|
+
module Conjur
|
2
|
+
module Policy
|
3
|
+
module Types
|
4
|
+
# An inheritable class attribute which is cloned by subclasses so the attribute
|
5
|
+
# can be a mutable thing such as a Hash.
|
6
|
+
#
|
7
|
+
# https://raw.githubusercontent.com/apotonick/uber/master/lib/uber/inheritable_attr.rb
|
8
|
+
module InheritableAttribute
|
9
|
+
def inheritable_attr(name, options={})
|
10
|
+
instance_eval %Q{
|
11
|
+
def #{name}=(v)
|
12
|
+
@#{name} = v
|
13
|
+
end
|
14
|
+
|
15
|
+
def #{name}
|
16
|
+
return @#{name} if instance_variable_defined?(:@#{name})
|
17
|
+
@#{name} = InheritableAttribute.inherit_for(self, :#{name}, #{options})
|
18
|
+
end
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.inherit_for(klass, name, options={})
|
23
|
+
return unless klass.superclass.respond_to?(name)
|
24
|
+
|
25
|
+
value = klass.superclass.send(name) # could be nil
|
26
|
+
|
27
|
+
return value if options[:clone] == false
|
28
|
+
Clone.(value) # this could be dynamic, allowing other inheritance strategies.
|
29
|
+
end
|
30
|
+
|
31
|
+
class Clone
|
32
|
+
# The second argument allows injecting more types.
|
33
|
+
def self.call(value, uncloneable=uncloneable())
|
34
|
+
uncloneable.each { |klass| return value if value.kind_of?(klass) }
|
35
|
+
value.clone
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.uncloneable
|
39
|
+
[Symbol, TrueClass, FalseClass, NilClass]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Methods which type-check and transform attributes. Type-checking can be done by
|
45
|
+
# duck-typing, with +is_a?+, or by a procedure.
|
46
|
+
module TypeChecking
|
47
|
+
# This is the primary function of the module.
|
48
|
+
#
|
49
|
+
# +value+ an input value
|
50
|
+
# +type_name+ used only for error messages.
|
51
|
+
# +test_function+ a class or function which will determine if the value is already the correct type.
|
52
|
+
# +converter+ if the +test_function+ fails, the converter is called to coerce the type.
|
53
|
+
# It should return +nil+ if its unable to do so.
|
54
|
+
def expect_type attr_name, value, type_name, test_function, converter = nil
|
55
|
+
if test_function.is_a?(Class)
|
56
|
+
cls = test_function
|
57
|
+
test_function = lambda{ value.is_a?(cls) }
|
58
|
+
end
|
59
|
+
if test_function.call
|
60
|
+
value
|
61
|
+
elsif converter && ( v = converter.call )
|
62
|
+
v
|
63
|
+
else
|
64
|
+
name = value.class.respond_to?(:short_name) ? value.class.short_name : value.class.name
|
65
|
+
raise "Expected a #{type_name} for field '#{attr_name}', got #{name}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Duck-type roles.
|
70
|
+
def test_role r
|
71
|
+
r.respond_to?(:role?) && r.role?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Duck-type resources.
|
75
|
+
def test_resource r
|
76
|
+
r.respond_to?(:resource?) && r.resource?
|
77
|
+
end
|
78
|
+
|
79
|
+
# If it's a Record
|
80
|
+
def expect_record name, value
|
81
|
+
expect_type name, value, "Record", lambda{ value.is_a?(Record) }
|
82
|
+
end
|
83
|
+
|
84
|
+
# If it's a Layer
|
85
|
+
def expect_layer name, value
|
86
|
+
expect_type name, value, "Layer", lambda{ value.is_a?(Layer) }
|
87
|
+
end
|
88
|
+
|
89
|
+
# If it looks like a resource.
|
90
|
+
def expect_resource name, value
|
91
|
+
expect_type name, value, "Resource", lambda{ test_resource value }
|
92
|
+
end
|
93
|
+
|
94
|
+
# If it looks like a role.
|
95
|
+
def expect_role name, value
|
96
|
+
expect_type name, value, "Role", lambda{ test_role value }
|
97
|
+
end
|
98
|
+
|
99
|
+
# +value+ may be a Member; Roles can also be converted to Members.
|
100
|
+
def expect_member name, value
|
101
|
+
expect_type name,
|
102
|
+
value,
|
103
|
+
"Member",
|
104
|
+
Member,
|
105
|
+
lambda{ Member.new(value) if test_role(value) }
|
106
|
+
end
|
107
|
+
|
108
|
+
# +value+ must be a Permission.
|
109
|
+
def expect_permission name, value
|
110
|
+
expect_type name,
|
111
|
+
value,
|
112
|
+
"Permission",
|
113
|
+
Permission
|
114
|
+
end
|
115
|
+
|
116
|
+
# +value+ must be a String.
|
117
|
+
def expect_string name, value
|
118
|
+
expect_type name,
|
119
|
+
value,
|
120
|
+
"string",
|
121
|
+
String
|
122
|
+
end
|
123
|
+
|
124
|
+
# +value+ must be a Integer.
|
125
|
+
def expect_integer name, value
|
126
|
+
expect_type name,
|
127
|
+
value,
|
128
|
+
"integer",
|
129
|
+
Integer
|
130
|
+
end
|
131
|
+
|
132
|
+
# +value+ can be a Hash, or an object which implements +to_h+.
|
133
|
+
def expect_hash name, value
|
134
|
+
expect_type name,
|
135
|
+
value,
|
136
|
+
"hash",
|
137
|
+
lambda{ value.is_a?(Hash)},
|
138
|
+
lambda{ value.to_h.stringify_keys if value.respond_to?(:to_h) }
|
139
|
+
end
|
140
|
+
|
141
|
+
# +v+ must be +true+ or +false+.
|
142
|
+
def expect_boolean name, v
|
143
|
+
v = true if v == "true"
|
144
|
+
v = false if v == "false"
|
145
|
+
expect_type name,
|
146
|
+
v,
|
147
|
+
"boolean",
|
148
|
+
lambda{ [ true, false ].member?(v) }
|
149
|
+
end
|
150
|
+
|
151
|
+
# +values+ can be an instance of +type+ (as determined by the type-checking methods), or
|
152
|
+
# it must be an array of them.
|
153
|
+
def expect_array name, kind, values
|
154
|
+
# Hash gets converted to an array of key/value pairs by Array
|
155
|
+
is_hash = values.kind_of?(Hash)
|
156
|
+
values = [values] if is_hash
|
157
|
+
|
158
|
+
result = Array(values).map do |v|
|
159
|
+
send "expect_#{kind}", name, v
|
160
|
+
end
|
161
|
+
|
162
|
+
(values.is_a?(Array) and not is_hash) ? result : result[0]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Define type-checked attributes, using the facilities defined in
|
167
|
+
# +TypeChecking+.
|
168
|
+
module AttributeDefinition
|
169
|
+
# Define a singular field.
|
170
|
+
#
|
171
|
+
# +attr+ the name of the field
|
172
|
+
# +kind+ the type of the field, which corresponds to a +TypeChecking+ method.
|
173
|
+
# +type+ a DSL object type which the parser should use to process the field.
|
174
|
+
# This option is not used for simple kinds like :boolean and :string, because they are
|
175
|
+
# not structured objects.
|
176
|
+
def define_field attr, kind, type = nil, dsl_accessor = false
|
177
|
+
register_yaml_field attr.to_s, type if type
|
178
|
+
register_field attr.to_s, kind if kind
|
179
|
+
|
180
|
+
if dsl_accessor
|
181
|
+
define_method attr do |*args|
|
182
|
+
v = args.shift
|
183
|
+
if v
|
184
|
+
existing = self.instance_variable_get("@#{attr}")
|
185
|
+
value = if existing
|
186
|
+
Array(existing) + [ v ]
|
187
|
+
else
|
188
|
+
v
|
189
|
+
end
|
190
|
+
self.instance_variable_set("@#{attr}", self.class.expect_array(attr, kind, value))
|
191
|
+
else
|
192
|
+
self.instance_variable_get("@#{attr}")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
else
|
196
|
+
define_method attr do
|
197
|
+
self.instance_variable_get("@#{attr}")
|
198
|
+
end
|
199
|
+
end
|
200
|
+
define_method "#{attr}=" do |v|
|
201
|
+
self.instance_variable_set("@#{attr}", self.class.expect_array(attr, kind, v))
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Define a plural field. A plural field is basically just an alias to the singular field.
|
206
|
+
# For example, a plural field called +members+ is really just an alias to +member+. Both
|
207
|
+
# +member+ and +members+ will accept single values or Arrays of values.
|
208
|
+
def define_plural_field attr, kind, type = nil, dsl_accessor = false
|
209
|
+
define_field attr, kind.to_s, type, dsl_accessor
|
210
|
+
|
211
|
+
register_yaml_field attr.to_s.pluralize, type if type
|
212
|
+
|
213
|
+
define_method attr.to_s.pluralize do |*args|
|
214
|
+
send attr, *args
|
215
|
+
end
|
216
|
+
define_method "#{attr.to_s.pluralize}=" do |v|
|
217
|
+
send "#{attr}=", v
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# This is the primary method used by concrete types to define their attributes.
|
222
|
+
#
|
223
|
+
# +attr+ the singularized attribute name.
|
224
|
+
#
|
225
|
+
# Options:
|
226
|
+
# +type+ a structured type to be constructed by the parser. If not provided, the type
|
227
|
+
# may be inferred from the attribute name (e.g. an attribute called :member is the type +Member+).
|
228
|
+
# +kind+ the symbolic name of the type. Inferred from the type, if the type is provided. Otherwise
|
229
|
+
# it's mandatory.
|
230
|
+
# +singular+ by default, attributes accept multiple values. This flag restricts the attribute
|
231
|
+
# to a single value only.
|
232
|
+
def attribute attr, options = {}
|
233
|
+
type = options[:type]
|
234
|
+
begin
|
235
|
+
type ||= Conjur::Policy::Types.const_get(attr.to_s.capitalize)
|
236
|
+
rescue NameError
|
237
|
+
end
|
238
|
+
type = nil if type == String
|
239
|
+
kind = options[:kind]
|
240
|
+
kind ||= type.short_name.downcase.to_sym if type
|
241
|
+
|
242
|
+
raise "Attribute :kind must be defined, explicitly or inferred from :type" unless kind
|
243
|
+
|
244
|
+
if options[:singular]
|
245
|
+
define_field attr, kind, type, options[:dsl_accessor]
|
246
|
+
else
|
247
|
+
define_plural_field attr, kind, type, options[:dsl_accessor]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Ruby type for attribute name.
|
252
|
+
def yaml_field_type name
|
253
|
+
self.yaml_fields[name]
|
254
|
+
end
|
255
|
+
|
256
|
+
# Is there a Ruby type for a named field?
|
257
|
+
def yaml_field? name
|
258
|
+
!!self.yaml_fields[name]
|
259
|
+
end
|
260
|
+
|
261
|
+
# Is there a semantic kind for a named field?
|
262
|
+
def field? name
|
263
|
+
!!self.fields[name]
|
264
|
+
end
|
265
|
+
|
266
|
+
protected
|
267
|
+
|
268
|
+
# +nodoc+
|
269
|
+
def register_yaml_field field_name, type
|
270
|
+
raise "YAML field #{field_name} already defined on #{self.name} as #{self.yaml_fields[field_name]}" if self.yaml_field?(field_name)
|
271
|
+
self.yaml_fields[field_name] = type
|
272
|
+
end
|
273
|
+
|
274
|
+
# +nodoc+
|
275
|
+
def register_field field_name, kind
|
276
|
+
raise "YAML field #{field_name} already defined on #{self.name} as #{self.fields[field_name]}" if self.field?(field_name)
|
277
|
+
self.fields[field_name] = kind
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Base class for implementing structured DSL object types such as Role, User, etc.
|
282
|
+
#
|
283
|
+
# To define a type:
|
284
|
+
#
|
285
|
+
# * Inherit from this class
|
286
|
+
# * Define attributes using +attribute+
|
287
|
+
#
|
288
|
+
# Your new type will automatically be registered with the YAML parser with a tag
|
289
|
+
# corresponding to the lower-cased short name of the class.
|
290
|
+
class Base
|
291
|
+
extend InheritableAttribute
|
292
|
+
extend TypeChecking
|
293
|
+
extend AttributeDefinition
|
294
|
+
|
295
|
+
# Stores a Markdown description of the type.
|
296
|
+
inheritable_attr :description
|
297
|
+
|
298
|
+
# Stores a YAML example for the type.
|
299
|
+
inheritable_attr :example
|
300
|
+
|
301
|
+
# Stores the mapping from attribute names to Ruby class names that will be constructed
|
302
|
+
# to populate the attribute.
|
303
|
+
inheritable_attr :yaml_fields
|
304
|
+
|
305
|
+
# Stores the mapping from attribute names to semantic kind names.
|
306
|
+
inheritable_attr :fields
|
307
|
+
|
308
|
+
# +nodoc+
|
309
|
+
self.yaml_fields = {}
|
310
|
+
|
311
|
+
# +nodoc+
|
312
|
+
self.fields = {}
|
313
|
+
|
314
|
+
# Things aren't roles by default
|
315
|
+
def role?
|
316
|
+
false
|
317
|
+
end
|
318
|
+
|
319
|
+
def id_attribute; 'id'; end
|
320
|
+
|
321
|
+
def custom_attribute_names
|
322
|
+
[ ]
|
323
|
+
end
|
324
|
+
|
325
|
+
def resource?
|
326
|
+
false
|
327
|
+
end
|
328
|
+
|
329
|
+
def role?
|
330
|
+
false
|
331
|
+
end
|
332
|
+
|
333
|
+
# Gets all 'child' records.
|
334
|
+
def referenced_records
|
335
|
+
result = []
|
336
|
+
instance_variables.map do |var|
|
337
|
+
value = instance_variable_get var
|
338
|
+
Array(value).each do |val|
|
339
|
+
result.push val if val.is_a?(Conjur::Policy::Types::Base)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
result.flatten
|
343
|
+
end
|
344
|
+
|
345
|
+
class << self
|
346
|
+
# Hook to register the YAML type.
|
347
|
+
def inherited cls
|
348
|
+
cls.register_yaml_type cls.short_name.underscore.gsub('_', '-')
|
349
|
+
end
|
350
|
+
|
351
|
+
# The last token in the ::-separated class name.
|
352
|
+
def short_name
|
353
|
+
self.name.demodulize
|
354
|
+
end
|
355
|
+
|
356
|
+
alias simple_name short_name
|
357
|
+
|
358
|
+
def register_yaml_type simple_name
|
359
|
+
::YAML.add_tag "!#{simple_name}", self
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Define DSL accessor for Role +member+ field.
|
365
|
+
module RoleMemberDSL
|
366
|
+
def self.included(base)
|
367
|
+
base.module_eval do
|
368
|
+
alias member_accessor member
|
369
|
+
|
370
|
+
def member r = nil, admin_option = false
|
371
|
+
if r
|
372
|
+
member = Member.new(r)
|
373
|
+
member.admin = true if admin_option == true
|
374
|
+
if self.member
|
375
|
+
self.member = Array(self.member).push(member)
|
376
|
+
else
|
377
|
+
self.member = member
|
378
|
+
end
|
379
|
+
else
|
380
|
+
member_accessor
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# Define DSL accessor for Resource +role+ field.
|
388
|
+
module ResourceMemberDSL
|
389
|
+
def self.included(base)
|
390
|
+
base.module_eval do
|
391
|
+
alias role_accessor role
|
392
|
+
|
393
|
+
def role r = nil, grant_option = nil
|
394
|
+
if r
|
395
|
+
role = Member.new(r)
|
396
|
+
role.admin = true if grant_option == true
|
397
|
+
if self.role
|
398
|
+
self.role = Array(self.role) + [ role ]
|
399
|
+
else
|
400
|
+
self.role = role
|
401
|
+
end
|
402
|
+
else
|
403
|
+
role_accessor
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
module AutomaticRoleDSL
|
411
|
+
def automatic_role record, role_name
|
412
|
+
AutomaticRole.new(record, role_name)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Conjur::Policy::Types
|
2
|
+
class Create < Base
|
3
|
+
attribute :record
|
4
|
+
|
5
|
+
self.description = %(
|
6
|
+
Create a record of any type.
|
7
|
+
|
8
|
+
A record can be a [Role](#reference/role) or a [Resource](#reference/resource).
|
9
|
+
|
10
|
+
Creating records can be done explicitly using this node type, or
|
11
|
+
implicitly. Examples of both are given immediately below.
|
12
|
+
|
13
|
+
When a record is created explicitly, it's an error if the record already exists.
|
14
|
+
When a record is created implicitly, the record will be found-or-created, and its
|
15
|
+
state (owner, fields and annotations) will be updated to match the policy declaration.
|
16
|
+
)
|
17
|
+
|
18
|
+
self.example = %(
|
19
|
+
- !user research # implicit record creation
|
20
|
+
- !create # explicit record creation
|
21
|
+
record: !user research
|
22
|
+
- !create
|
23
|
+
record: !group experiment
|
24
|
+
- !create
|
25
|
+
record: !role control
|
26
|
+
kind: experimental_control
|
27
|
+
owner: !user research
|
28
|
+
)
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
messages = [ "Create #{record}" ]
|
32
|
+
if record.resource?
|
33
|
+
(record.annotations||{}).each do |k,v|
|
34
|
+
messages.push " Set annotation '#{k}'"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
messages.join("\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|