conjur-asset-dsl2 0.3.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +2 -0
  3. data/.gitignore +14 -0
  4. data/.project +18 -0
  5. data/.rspec +1 -0
  6. data/.travis.yml +4 -0
  7. data/CHANGELOG +1 -0
  8. data/Dockerfile.dev +19 -0
  9. data/Gemfile +4 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +248 -0
  12. data/Rakefile +18 -0
  13. data/backup.tar +0 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +7 -0
  16. data/conjur-asset-dsl2.gemspec +32 -0
  17. data/jenkins.sh +36 -0
  18. data/lib/conjur/command/dsl2.rb +175 -0
  19. data/lib/conjur/dsl2/executor/base.rb +50 -0
  20. data/lib/conjur/dsl2/executor/create.rb +117 -0
  21. data/lib/conjur/dsl2/executor/deny.rb +13 -0
  22. data/lib/conjur/dsl2/executor/give.rb +12 -0
  23. data/lib/conjur/dsl2/executor/grant.rb +13 -0
  24. data/lib/conjur/dsl2/executor/permit.rb +16 -0
  25. data/lib/conjur/dsl2/executor/retire.rb +7 -0
  26. data/lib/conjur/dsl2/executor/revoke.rb +11 -0
  27. data/lib/conjur/dsl2/executor/update.rb +31 -0
  28. data/lib/conjur/dsl2/executor.rb +99 -0
  29. data/lib/conjur/dsl2/invalid.rb +12 -0
  30. data/lib/conjur/dsl2/plan.rb +49 -0
  31. data/lib/conjur/dsl2/planner/base.rb +215 -0
  32. data/lib/conjur/dsl2/planner/grants.rb +85 -0
  33. data/lib/conjur/dsl2/planner/permissions.rb +80 -0
  34. data/lib/conjur/dsl2/planner/record.rb +102 -0
  35. data/lib/conjur/dsl2/planner.rb +38 -0
  36. data/lib/conjur/dsl2/ruby/loader.rb +263 -0
  37. data/lib/conjur/dsl2/types/base.rb +376 -0
  38. data/lib/conjur/dsl2/types/create.rb +15 -0
  39. data/lib/conjur/dsl2/types/deny.rb +17 -0
  40. data/lib/conjur/dsl2/types/give.rb +14 -0
  41. data/lib/conjur/dsl2/types/grant.rb +24 -0
  42. data/lib/conjur/dsl2/types/member.rb +14 -0
  43. data/lib/conjur/dsl2/types/permit.rb +22 -0
  44. data/lib/conjur/dsl2/types/policy.rb +129 -0
  45. data/lib/conjur/dsl2/types/records.rb +243 -0
  46. data/lib/conjur/dsl2/types/retire.rb +14 -0
  47. data/lib/conjur/dsl2/types/revoke.rb +14 -0
  48. data/lib/conjur/dsl2/types/update.rb +16 -0
  49. data/lib/conjur/dsl2/yaml/handler.rb +400 -0
  50. data/lib/conjur/dsl2/yaml/loader.rb +29 -0
  51. data/lib/conjur-asset-dsl2-version.rb +7 -0
  52. data/lib/conjur-asset-dsl2.rb +27 -0
  53. data/syntax.md +147 -0
  54. metadata +237 -0
@@ -0,0 +1,376 @@
1
+ module Conjur
2
+ module DSL2
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 value, type_name, test_function, converter = nil
55
+
56
+ if test_function.is_a?(Class)
57
+ cls = test_function
58
+ test_function = lambda{ value.is_a?(cls) }
59
+ end
60
+ if test_function.call
61
+ value
62
+ elsif converter && ( v = converter.call )
63
+ v
64
+ else
65
+ name = value.class.respond_to?(:short_name) ? value.class.short_name : value.class.name
66
+ raise "Expecting #{type_name}, got #{name}"
67
+ end
68
+ end
69
+
70
+ # Duck-type roles.
71
+ def test_role r
72
+ r.respond_to?(:role?) && r.role?
73
+ end
74
+
75
+ # Duck-type resources.
76
+ def test_resource r
77
+ r.respond_to?(:resource?) && r.resource?
78
+ end
79
+
80
+ # If it's a Record
81
+ def expect_record value
82
+ expect_type value, "Record", lambda{ value.is_a?(Record) }
83
+ end
84
+
85
+ # If it's a Layer
86
+ def expect_layer value
87
+ expect_type value, "Layer", lambda{ value.is_a?(Layer) }
88
+ end
89
+
90
+ # If it looks like a resource.
91
+ def expect_resource value
92
+ expect_type value, "Resource", lambda{ test_resource value }
93
+ end
94
+
95
+ # If it looks like a role.
96
+ def expect_role value
97
+ expect_type value, "Role", lambda{ test_role value }
98
+ end
99
+
100
+ # +value+ may be a Member; Roles can also be converted to Members.
101
+ def expect_member value
102
+ expect_type 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 value
110
+ expect_type value,
111
+ "Permission",
112
+ Permission
113
+ end
114
+
115
+ # +value+ must be a String.
116
+ def expect_string value
117
+ expect_type value,
118
+ "string",
119
+ String
120
+ end
121
+
122
+ # +value+ must be a Integer.
123
+ def expect_integer value
124
+ expect_type value,
125
+ "integer",
126
+ Integer
127
+ end
128
+
129
+ # +value+ can be a Hash, or an object which implements +to_h+.
130
+ def expect_hash value
131
+ expect_type value,
132
+ "hash",
133
+ lambda{ value.is_a?(Hash)},
134
+ lambda{ value.to_h.stringify_keys if value.respond_to?(:to_h) }
135
+ end
136
+
137
+ # +v+ must be +true+ or +false+.
138
+ def expect_boolean v
139
+ v = true if v == "true"
140
+ v = false if v == "false"
141
+ expect_type v,
142
+ "boolean",
143
+ lambda{ [ true, false ].member?(v) }
144
+ end
145
+
146
+ # +values+ can be an instance of +type+ (as determined by the type-checking methods), or
147
+ # it must be an array of them.
148
+ def expect_array kind, values
149
+ # Hash gets converted to an array of key/value pairs by Array
150
+ is_hash = values.kind_of?(Hash)
151
+ values = [values] if is_hash
152
+
153
+ result = Array(values).map do |v|
154
+ send "expect_#{kind}", v
155
+ end
156
+
157
+ (values.is_a?(Array) and not is_hash) ? result : result[0]
158
+ end
159
+ end
160
+
161
+ # Define type-checked attributes, using the facilities defined in
162
+ # +TypeChecking+.
163
+ module AttributeDefinition
164
+ # Define a singular field.
165
+ #
166
+ # +attr+ the name of the field
167
+ # +kind+ the type of the field, which corresponds to a +TypeChecking+ method.
168
+ # +type+ a DSL object type which the parser should use to process the field.
169
+ # This option is not used for simple kinds like :boolean and :string, because they are
170
+ # not structured objects.
171
+ def define_field attr, kind, type = nil, dsl_accessor = false
172
+ register_yaml_field attr.to_s, type if type
173
+
174
+ if dsl_accessor
175
+ define_method attr do |*args|
176
+ v = args.shift
177
+ if v
178
+ existing = self.instance_variable_get("@#{attr}")
179
+ value = if existing
180
+ Array(existing) + [ v ]
181
+ else
182
+ v
183
+ end
184
+ self.instance_variable_set("@#{attr}", self.class.expect_array(kind, value))
185
+ else
186
+ self.instance_variable_get("@#{attr}")
187
+ end
188
+ end
189
+ else
190
+ define_method attr do
191
+ self.instance_variable_get("@#{attr}")
192
+ end
193
+ end
194
+ define_method "#{attr}=" do |v|
195
+ self.instance_variable_set("@#{attr}", self.class.expect_array(kind, v))
196
+ end
197
+ end
198
+
199
+ # Define a plural field. A plural field is basically just an alias to the singular field.
200
+ # For example, a plural field called +members+ is really just an alias to +member+. Both
201
+ # +member+ and +members+ will accept single values or Arrays of values.
202
+ def define_plural_field attr, kind, type = nil, dsl_accessor = false
203
+ define_field attr, kind.to_s, type, dsl_accessor
204
+
205
+ register_yaml_field attr.to_s.pluralize, type if type
206
+
207
+ define_method attr.to_s.pluralize do |*args|
208
+ send attr, *args
209
+ end
210
+ define_method "#{attr.to_s.pluralize}=" do |v|
211
+ send "#{attr}=", v
212
+ end
213
+ end
214
+
215
+ # This is the primary method used by concrete types to define their attributes.
216
+ #
217
+ # +attr+ the singularized attribute name.
218
+ #
219
+ # Options:
220
+ # +type+ a structured type to be constructed by the parser. If not provided, the type
221
+ # may be inferred from the attribute name (e.g. an attribute called :member is the type +Member+).
222
+ # +kind+ the symbolic name of the type. Inferred from the type, if the type is provided. Otherwise
223
+ # it's mandatory.
224
+ # +singular+ by default, attributes accept multiple values. This flag restricts the attribute
225
+ # to a single value only.
226
+ def attribute attr, options = {}
227
+ type = options[:type]
228
+ begin
229
+ type ||= Conjur::DSL2::Types.const_get(attr.to_s.capitalize)
230
+ rescue NameError
231
+ end
232
+ kind = options[:kind]
233
+ kind ||= type.short_name.downcase.to_sym if type
234
+
235
+ raise "Attribute :kind must be defined, explicitly or inferred from :type" unless kind
236
+
237
+ if options[:singular]
238
+ define_field attr, kind, type, options[:dsl_accessor]
239
+ else
240
+ define_plural_field attr, kind, type, options[:dsl_accessor]
241
+ end
242
+ end
243
+
244
+ # Ruby type for attribute name.
245
+ def yaml_field_type name
246
+ self.yaml_fields[name]
247
+ end
248
+
249
+ # Is there a Ruby type for a named field?
250
+ def yaml_field? name
251
+ !!self.yaml_fields[name]
252
+ end
253
+
254
+ protected
255
+
256
+ # +nodoc+
257
+ def register_yaml_field field_name, type
258
+ raise "YAML field #{field_name} already defined on #{self.name} as #{self.yaml_fields[field_name]}" if self.yaml_field?(field_name)
259
+ self.yaml_fields[field_name] = type
260
+ end
261
+ end
262
+
263
+ # Base class for implementing structured DSL object types such as Role, User, etc.
264
+ #
265
+ # To define a type:
266
+ #
267
+ # * Inherit from this class
268
+ # * Define attributes using +attribute+
269
+ #
270
+ # Your new type will automatically be registered with the YAML parser with a tag
271
+ # corresponding to the lower-cased short name of the class.
272
+ class Base
273
+ extend InheritableAttribute
274
+ extend TypeChecking
275
+ extend AttributeDefinition
276
+
277
+ # On creation, an owner can always be specified.
278
+ attr_accessor :owner
279
+
280
+ # Stores the mapping from attribute names to Ruby class names that will be constructed
281
+ # to populate the attribute.
282
+ inheritable_attr :yaml_fields
283
+
284
+ # +nodoc+
285
+ self.yaml_fields = {}
286
+
287
+ # Things aren't roles by default
288
+ def role?
289
+ false
290
+ end
291
+
292
+ def id_attribute; 'id'; end
293
+
294
+ def custom_attribute_names
295
+ [ ]
296
+ end
297
+
298
+ def resource?
299
+ false
300
+ end
301
+
302
+ def role?
303
+ false
304
+ end
305
+
306
+ class << self
307
+ # Hook to register the YAML type.
308
+ def inherited cls
309
+ cls.register_yaml_type cls.short_name.underscore.gsub('_', '-')
310
+ end
311
+
312
+ # The last token in the ::-separated class name.
313
+ def short_name
314
+ self.name.demodulize
315
+ end
316
+
317
+ def register_yaml_type simple_name
318
+ ::YAML.add_tag "!#{simple_name}", self
319
+ end
320
+ end
321
+ end
322
+
323
+ # Define DSL accessor for Role +member+ field.
324
+ module RoleMemberDSL
325
+ def self.included(base)
326
+ base.module_eval do
327
+ alias member_accessor member
328
+
329
+ def member r = nil, admin_option = false
330
+ if r
331
+ member = Member.new(r)
332
+ member.admin = true if admin_option == true
333
+ if self.member
334
+ self.member = Array(self.member).push(member)
335
+ else
336
+ self.member = member
337
+ end
338
+ else
339
+ member_accessor
340
+ end
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ # Define DSL accessor for Resource +role+ field.
347
+ module ResourceMemberDSL
348
+ def self.included(base)
349
+ base.module_eval do
350
+ alias role_accessor role
351
+
352
+ def role r = nil, grant_option = nil
353
+ if r
354
+ role = Member.new(r)
355
+ role.admin = true if grant_option == true
356
+ if self.role
357
+ self.role = Array(self.role) + [ role ]
358
+ else
359
+ self.role = role
360
+ end
361
+ else
362
+ role_accessor
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end
368
+
369
+ module ManagedRoleDSL
370
+ def managed_role record, role_name
371
+ ManagedRole.new(record, role_name)
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end
@@ -0,0 +1,15 @@
1
+ module Conjur::DSL2::Types
2
+ class Create < Base
3
+ attribute :record
4
+
5
+ def to_s
6
+ messages = [ "Create #{record}" ]
7
+ if record.resource?
8
+ (record.annotations||{}).each do |k,v|
9
+ messages.push " Set annotation '#{k}'"
10
+ end
11
+ end
12
+ messages.join("\n")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ class Deny < Base
5
+ attribute :role, kind: :role, dsl_accessor: true
6
+ attribute :privilege, kind: :string, dsl_accessor: true
7
+ attribute :resource, dsl_accessor: true
8
+
9
+ include ResourceMemberDSL
10
+
11
+ def to_s
12
+ "Deny #{role} to '#{privilege}' #{resource}"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ class Give < Base
5
+ attribute :resource, kind: :resource
6
+ attribute :owner, kind: :role
7
+
8
+ def to_s
9
+ "Give #{resource} to #{owner}"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ class Grant < Base
5
+ attribute :role, dsl_accessor: true
6
+ attribute :member
7
+ attribute :replace, kind: :boolean, singular: true, dsl_accessor: true
8
+
9
+ include RoleMemberDSL
10
+ include ManagedRoleDSL
11
+
12
+ def to_s
13
+ role_str = role.kind_of?(Array) ?
14
+ role.join(', ') : role
15
+ member_str = member.kind_of?(Array) ?
16
+ member.map(&:role).join(', ') : member.role
17
+ admin = member.kind_of?(Array) ?
18
+ member.map(&:admin).all? : member.admin
19
+ "Grant #{role_str} to #{member_str}#{replace ? ' exclusively ' : ''}#{admin ? ' with admin option' : ''}"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ class Member < Base
5
+ def initialize role = nil
6
+ self.role = role
7
+ end
8
+
9
+ attribute :role
10
+ attribute :admin, kind: :boolean, singular: true
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ class Permit < Base
5
+ attribute :role, kind: :member
6
+ attribute :privilege, kind: :string, dsl_accessor: true
7
+ attribute :resource, dsl_accessor: true
8
+ attribute :replace, kind: :boolean, singular: true, dsl_accessor: true
9
+
10
+ include ResourceMemberDSL
11
+
12
+ def initialize privilege = nil
13
+ self.privilege = privilege
14
+ end
15
+
16
+ def to_s
17
+ "Permit #{role.role} to '#{privilege}' #{resource}#{role.admin ? ' with grant option' : ''}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,129 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ class YAMLList < Array
5
+ def tag
6
+ [ "!", self.class.name.split("::")[-1].underscore ].join
7
+ end
8
+
9
+ def encode_with coder
10
+ coder.represent_seq tag, self
11
+ end
12
+ end
13
+
14
+ module Tagless
15
+ def tag; nil; end
16
+ end
17
+
18
+ module CustomStatement
19
+ def custom_statement handler, &block
20
+ record = yield
21
+ class << record
22
+ include RecordReferenceFactory
23
+ end
24
+ push record
25
+ do_scope record, &handler
26
+ end
27
+ end
28
+
29
+ module Grants
30
+ include CustomStatement
31
+
32
+ def grant &block
33
+ custom_statement(block) do
34
+ Conjur::DSL2::Types::Grant.new
35
+ end
36
+ end
37
+
38
+ def revoke &block
39
+ custom_statement(block) do
40
+ Conjur::DSL2::Types::Revoke.new
41
+ end
42
+ end
43
+ end
44
+
45
+ module Permissions
46
+ include CustomStatement
47
+
48
+ def permit privilege, &block
49
+ custom_statement(block) do
50
+ Conjur::DSL2::Types::Permit.new(privilege)
51
+ end
52
+ end
53
+
54
+ def give &block
55
+ custom_statement(block) do
56
+ Conjur::DSL2::Types::Give.new
57
+ end
58
+ end
59
+
60
+ def retire &block
61
+ custom_statement(block) do
62
+ Conjur::DSL2::Types::Retire.new
63
+ end
64
+ end
65
+ end
66
+
67
+ # Entitlements will allow creation of any record, as well as declaration
68
+ # of permit, deny, grant and revoke.
69
+ class Entitlements < YAMLList
70
+ include Tagless
71
+ include Grants
72
+ include Permissions
73
+
74
+ def policy id=nil, &block
75
+ policy = Policy.new
76
+ policy.id(id) unless id.nil?
77
+ push policy
78
+
79
+ do_scope policy, &block
80
+ end
81
+ end
82
+
83
+ class Body < YAMLList
84
+ include Grants
85
+ include Permissions
86
+ end
87
+
88
+ # Policy includes the functionality of Entitlements, wrapped in a
89
+ # policy role, policy resource, policy id and policy version.
90
+ class Policy < Record
91
+ include ActsAsResource
92
+ include ActsAsRole
93
+
94
+ def role default_account
95
+ Role.new("#{self.account || default_account}:policy:#{id}", default_account: default_account)
96
+ end
97
+
98
+ def resource default_account
99
+ Resource.new("#{self.account || default_account}:policy:#{id}", default_account: default_account)
100
+ end
101
+
102
+ def body &block
103
+ if block_given?
104
+ singleton :body, lambda { Body.new }, &block
105
+ end
106
+ @body
107
+ end
108
+
109
+ def body= body
110
+ @body = body
111
+ end
112
+
113
+ protected
114
+
115
+ def singleton id, factory, &block
116
+ object = instance_variable_get("@#{id}")
117
+ unless object
118
+ object = factory.call
119
+ class << object
120
+ include Tagless
121
+ end
122
+ instance_variable_set("@#{id}", object)
123
+ end
124
+ do_scope object, &block
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end