conjur-asset-dsl2 0.3.0

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