conjur-policy-parser 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|