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,243 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ # A createable record type.
5
+ class Record < Base
6
+ def role?
7
+ false
8
+ end
9
+ def resource?
10
+ false
11
+ end
12
+ end
13
+
14
+ module ActsAsResource
15
+ def self.included(base)
16
+ base.module_eval do
17
+ attribute :id, kind: :string, singular: true, dsl_accessor: true
18
+ attribute :account, kind: :string, singular: true
19
+ attribute :owner, kind: :role, singular: true, dsl_accessor: true
20
+
21
+ attribute :annotations, kind: :hash, type: Hash, singular: true
22
+
23
+ def description value
24
+ annotation 'description', value
25
+ end
26
+
27
+ def annotation name, value
28
+ self.annotations ||= {}
29
+ self.annotations[name] = value
30
+ end
31
+ end
32
+ end
33
+
34
+ def initialize id = nil
35
+ self.id = id if id
36
+ end
37
+
38
+ def to_s
39
+ "#{resource_kind.gsub('_', ' ')} '#{id}'#{account ? ' in account \'' + account + '\'': ''}"
40
+ end
41
+
42
+ def resourceid default_account = nil
43
+ [ account || default_account, resource_kind, id ].join(":")
44
+ end
45
+
46
+ def resource_kind
47
+ self.class.name.split("::")[-1].underscore
48
+ end
49
+
50
+ def resource_id
51
+ id
52
+ end
53
+
54
+ def action
55
+ :create
56
+ end
57
+
58
+ def resource?
59
+ true
60
+ end
61
+
62
+ def immutable_attribute_names
63
+ []
64
+ end
65
+
66
+ end
67
+
68
+ module ActsAsRole
69
+ def roleid default_account
70
+ [ account || default_account, role_kind, id ].join(":")
71
+ end
72
+
73
+ def role?
74
+ true
75
+ end
76
+
77
+ def role_kind
78
+ self.class.name.split("::")[-1].underscore
79
+ end
80
+
81
+ def role_id
82
+ id
83
+ end
84
+ end
85
+
86
+ module ActsAsCompoundId
87
+ def initialize kind_or_id = nil, id_or_options = nil
88
+ if kind_or_id && id_or_options && id_or_options.is_a?(String)
89
+ self.kind = kind_or_id
90
+ self.id = id_or_options
91
+ elsif kind_or_id && kind_or_id.index(":")
92
+ id_or_options ||= {}
93
+ account, self.kind, self.id = kind_or_id.split(':', 3)
94
+ self.account = account if account != id_or_options[:default_account]
95
+ end
96
+ end
97
+
98
+ def == other
99
+ other.kind_of?(ActsAsCompoundId) && kind == other.kind && id == other.id && account == other.account
100
+ end
101
+
102
+ def to_s
103
+ "#{kind} #{self.class.short_name.underscore} '#{id}'#{account ? ' in account \'' + account + '\'': ''}"
104
+ end
105
+ end
106
+
107
+ class Role < Record
108
+ include ActsAsRole
109
+ include ActsAsCompoundId
110
+
111
+ attribute :id, kind: :string, singular: true, dsl_accessor: true
112
+ attribute :kind, kind: :string, singular: true, dsl_accessor: true
113
+ attribute :account, kind: :string, singular: true
114
+ attribute :owner, kind: :role, singular: true, dsl_accessor: true
115
+
116
+ def roleid default_account = nil
117
+ raise "account is required" unless account || default_account
118
+ [ account || default_account, kind, id ].join(":")
119
+ end
120
+
121
+ def role_id; id; end
122
+ def role_kind; kind; end
123
+
124
+ def immutable_attribute_names
125
+ []
126
+ end
127
+ end
128
+
129
+ class Resource < Record
130
+ include ActsAsResource
131
+ include ActsAsCompoundId
132
+
133
+ attribute :kind, kind: :string, singular: true, dsl_accessor: true
134
+
135
+ def resource_kind
136
+ kind
137
+ end
138
+ end
139
+
140
+ class User < Record
141
+ include ActsAsResource
142
+ include ActsAsRole
143
+
144
+ attribute :uidnumber, kind: :integer, singular: true, dsl_accessor: true
145
+
146
+ def id_attribute; 'login'; end
147
+
148
+ def custom_attribute_names
149
+ [ :uidnumber ]
150
+ end
151
+ end
152
+
153
+ class Group < Record
154
+ include ActsAsResource
155
+ include ActsAsRole
156
+
157
+ attribute :gidnumber, kind: :integer, singular: true, dsl_accessor: true
158
+
159
+ def custom_attribute_names
160
+ [ :gidnumber ]
161
+ end
162
+ end
163
+
164
+ class Host < Record
165
+ include ActsAsResource
166
+ include ActsAsRole
167
+ end
168
+
169
+ class Layer < Record
170
+ include ActsAsResource
171
+ include ActsAsRole
172
+ end
173
+
174
+ class Variable < Record
175
+ include ActsAsResource
176
+
177
+ attribute :kind, kind: :string, singular: true, dsl_accessor: true
178
+ attribute :mime_type, kind: :string, singular: true, dsl_accessor: true
179
+
180
+ def custom_attribute_names
181
+ [ :kind, :mime_type ]
182
+ end
183
+
184
+ def immutable_attribute_names
185
+ [ :kind, :mime_type ]
186
+ end
187
+ end
188
+
189
+ class Webservice < Record
190
+ include ActsAsResource
191
+ end
192
+
193
+ class HostFactory < Record
194
+ include ActsAsResource
195
+
196
+ attribute :role, kind: :role, dsl_accessor: true, singular: true
197
+ attribute :layer, kind: :layer, dsl_accessor: true
198
+ end
199
+
200
+ class ManagedRole < Base
201
+ include ActsAsRole
202
+
203
+ def initialize record = nil, role_name = nil
204
+ self.record = record if record
205
+ self.role_name = role_name if role_name
206
+ end
207
+
208
+ attribute :record, kind: :role, singular: true
209
+ attribute :role_name, kind: :string, singular: true
210
+
211
+ class << self
212
+ def build fullid, default_account
213
+ account, kind, id = fullid.split(':', 3)
214
+ raise "Expecting @ for kind, got #{kind}" unless kind == "@"
215
+ record_kind, record_id, role_name = id.split('/', 3)
216
+ record = Conjur::DSL2::Types.const_get(record_kind.classify).new.tap do |record|
217
+ record.id = record_id
218
+ record.account = account unless account == default_account
219
+ end
220
+ self.new record, role_name
221
+ end
222
+ end
223
+
224
+ def to_s
225
+ role_name = self.id.split('/')[-1]
226
+ "'#{role_name}' on #{record}"
227
+ end
228
+
229
+ def account
230
+ record.account
231
+ end
232
+
233
+ def role_kind
234
+ "@"
235
+ end
236
+
237
+ def id
238
+ [ record.role_kind, record.id, role_name ].join('/')
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,14 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ class Retire < Base
5
+ attribute :record, kind: :resource
6
+
7
+ def to_s
8
+ "Retire #{record}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,14 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Types
4
+ class Revoke < Base
5
+ attribute :role, dsl_accessor: true
6
+ attribute :member, kind: :role, dsl_accessor: true
7
+
8
+ def to_s
9
+ "Revoke #{role} from #{member}"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Conjur::DSL2::Types
2
+ class Update < Base
3
+ attribute :record
4
+
5
+ def to_s
6
+ messages = [ "Update #{record}" ]
7
+ (record.custom_attribute_names||[]).each do |k|
8
+ messages.push " Set field '#{k}'"
9
+ end
10
+ (record.annotations||{}).each do |k,v|
11
+ messages.push " Set annotation '#{k}'"
12
+ end
13
+ messages.join("\n")
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,400 @@
1
+ module Conjur
2
+ module DSL2
3
+ module YAML
4
+ class Handler < Psych::Handler
5
+ attr_accessor :parser, :filename, :result
6
+
7
+ # Override the logger with this method.
8
+ cattr_accessor :logger
9
+
10
+ require 'logger'
11
+
12
+ self.logger = Logger.new(STDERR)
13
+ self.logger.level = Logger::INFO
14
+
15
+ # An abstract Base handler. The handler will receive each document message within
16
+ # its particular context (sequence, mapping, etc).
17
+ #
18
+ # The handler can decide that the message is not allowed by not implementing the message.
19
+ #
20
+ class Base
21
+ attr_reader :parent
22
+
23
+ def initialize parent
24
+ @parent = parent
25
+ end
26
+
27
+ # Each handler should implement this method to return the result object (which may only be
28
+ # partially constructed). This method is used by the root handler to associate the handler
29
+ # result with an anchor (if applicable).
30
+ def result; raise "Not implemented"; end
31
+
32
+ # Handlers are organized in a stack. Each handler can find the root Handler by traversing up the stack.
33
+ def handler
34
+ parent.handler
35
+ end
36
+
37
+ # Push this handler onto the stack.
38
+ def push_handler
39
+ handler.push_handler self
40
+ end
41
+
42
+ # Pop this handler off the stack, indicating that it's complete.
43
+ def pop_handler
44
+ handler.pop_handler
45
+ end
46
+
47
+ # An alias is encountered in the document. The value may be looked up in the root Handler +anchor+ hash.
48
+ def alias anchor
49
+ raise "Unexpected alias #{anchor}"
50
+ end
51
+
52
+ # Start a new mapping with the specified tag.
53
+ # If the handler wants to accept the message, it should return a new handler.
54
+ def start_mapping tag
55
+ raise "Unexpected mapping"
56
+ end
57
+
58
+ # Start a new sequence.
59
+ # If the handler wants to accept the message, it should return a new handler.
60
+ def start_sequence
61
+ raise "Unexpected sequence"
62
+ end
63
+
64
+ # End the current sequence. The handler should populate the sequence into the parent handler.
65
+ def end_sequence
66
+ raise "Unexpected end of sequence"
67
+ end
68
+
69
+ # End the current mapping. The handler should populate the mapping into the parent handler.
70
+ def end_mapping
71
+ raise "Unexpected end of mapping"
72
+ end
73
+
74
+ # Process a scalar value. It may be a map key, a map value, or a sequence value.
75
+ # The handler should return a result from this method, so that the root Handler can
76
+ # associate it with an anchor, if any.
77
+ def scalar value, tag, quoted
78
+ raise "Unexpected scalar"
79
+ end
80
+
81
+ protected
82
+
83
+ def scalar_value value, tag, quoted, record_type
84
+ if type = type_of(tag, record_type)
85
+ type.new.tap do |record|
86
+ record.id = value
87
+ end
88
+ else
89
+ SafeYAML::Transform.to_guessed_type(value, quoted, SafeYAML::OPTIONS)
90
+ end
91
+ end
92
+
93
+ def type_of tag, record_type
94
+ if tag && tag.match(/!(.*)/)
95
+ type_name = $1.underscore.camelize
96
+ begin
97
+ Conjur::DSL2::Types.const_get(type_name)
98
+ rescue NameError
99
+ raise "Unrecognized data type '#{tag}'"
100
+ end
101
+ else
102
+ record_type
103
+ end
104
+ end
105
+ end
106
+
107
+ # Handles the root document, which should be a sequence.
108
+ class Root < Base
109
+ attr_reader :result, :handler
110
+
111
+ def initialize handler
112
+ super nil
113
+
114
+ @handler = handler
115
+ @result = nil
116
+ end
117
+
118
+ def handler; @handler; end
119
+
120
+ def sequence seq
121
+ raise "Already got sequence result" if @result
122
+ @result = seq
123
+ end
124
+
125
+ # The document root is expected to start with a sequence.
126
+ # A Sequence handler is constructed with no implicit type. This
127
+ # sub-handler handles the message.
128
+ def start_sequence
129
+ Sequence.new(self, nil).tap do |h|
130
+ h.push_handler
131
+ end.result
132
+ end
133
+
134
+ # Finish the sequence, and the document.
135
+ def end_sequence
136
+ pop_handler
137
+ end
138
+ end
139
+
140
+ # Handles a sequence. The sequence has:
141
+ # +record_type+ default record type, inferred from the field name on the parent record.
142
+ # +args+ the start_sequence arguments.
143
+ class Sequence < Base
144
+ attr_reader :record_type
145
+
146
+ def initialize parent, record_type
147
+ super parent
148
+
149
+ @record_type = record_type
150
+ @list = []
151
+ end
152
+
153
+ def result; @list; end
154
+
155
+ # Adds a mapping to the sequence.
156
+ def mapping value
157
+ @list.push value
158
+ end
159
+
160
+ # Adds a sequence to the sequence.
161
+ def sequence value
162
+ @list.push value
163
+ end
164
+
165
+ # When the sequence receives an alias, the alias should be mapped to the previously stored
166
+ # value and added to the result list.
167
+ def alias anchor
168
+ @list.push handler.anchor(anchor)
169
+ end
170
+
171
+ # When the sequence contains a mapping, a new record should be created corresponding to either:
172
+ #
173
+ # * The explicit stated type (tag) of the mapping
174
+ # * The implicit field type of the sequence
175
+ #
176
+ # If neither of these is available, it's an error.
177
+ def start_mapping tag
178
+ if type = type_of(tag, record_type)
179
+ Mapping.new(self, type).tap do |h|
180
+ h.push_handler
181
+ end.result
182
+ else
183
+ raise "No type given or inferred for sequence entry"
184
+ end
185
+ end
186
+
187
+ # Process a sequence within a sequence.
188
+ def start_sequence
189
+ Sequence.new(self, record_type).tap do |h|
190
+ h.push_handler
191
+ end.result
192
+ end
193
+
194
+ # When the sequence contains a scalar, the value should be appended to the result.
195
+ def scalar value, tag, quoted
196
+ scalar_value(value, tag, quoted, record_type).tap do |value|
197
+ @list.push value
198
+ end
199
+ end
200
+
201
+ def end_sequence
202
+ parent.sequence @list
203
+ pop_handler
204
+ end
205
+ end
206
+
207
+ # Handles a mapping, each of which will be parsed into a structured record.
208
+ class Mapping < Base
209
+ attr_reader :type
210
+
211
+ def initialize parent, type
212
+ super parent
213
+
214
+ @record = type.new
215
+ end
216
+
217
+ def result; @record; end
218
+
219
+ def map_entry key, value
220
+ if @record.respond_to?(:[]=)
221
+ @record.send(:[]=, key, value)
222
+ else
223
+ begin
224
+ @record.send("#{key}=", value)
225
+ rescue NoMethodError
226
+ raise "No such attribute '#{key}' on type #{@record.class.short_name}"
227
+ end
228
+ end
229
+ end
230
+
231
+ # Begins a mapping with the anchor value as the key.
232
+ def alias anchor
233
+ key = handler.anchor(anchor)
234
+ MapEntry.new(self, @record, key).tap do |h|
235
+ h.push_handler
236
+ end.result
237
+ end
238
+
239
+ # Begins a new map entry.
240
+ def scalar value, tag, quoted
241
+ value = scalar_value(value, tag, quoted, type)
242
+ MapEntry.new(self, @record, value).tap do |h|
243
+ h.push_handler
244
+ end.result
245
+ end
246
+
247
+ def end_mapping
248
+ parent.mapping @record
249
+ pop_handler
250
+ end
251
+ end
252
+
253
+ # Processes a map entry. At this point, the parent record and the map key are known.
254
+ class MapEntry < Base
255
+ attr_reader :record, :key
256
+
257
+ def initialize parent, record, key
258
+ super parent
259
+
260
+ @record = record
261
+ @key = key
262
+ end
263
+
264
+ def result; nil; end
265
+
266
+ def sequence value
267
+ value value
268
+ end
269
+
270
+ def mapping value
271
+ value value
272
+ end
273
+
274
+ def value value
275
+ parent.map_entry @key, value
276
+ pop_handler
277
+ end
278
+
279
+ # Interpret the alias as the map value and populate in the parent.
280
+ def alias anchor
281
+ value handler.anchor(anchor)
282
+ end
283
+
284
+ # Start a mapping as a map value.
285
+ def start_mapping tag
286
+ if type = type_of(tag, yaml_field_type(key))
287
+ Mapping.new(self, type).tap do |h|
288
+ h.push_handler
289
+ end.result
290
+ else
291
+ # We got a mapping on a simple type
292
+ raise "Attribute '#{key}' can't be a mapping"
293
+ end
294
+ end
295
+
296
+ # Start a sequence as a map value.
297
+ def start_sequence
298
+ Sequence.new(self, yaml_field_type(key)).tap do |h|
299
+ h.push_handler
300
+ end.result
301
+ end
302
+
303
+ def scalar value, tag, quoted
304
+ value scalar_value(value, tag, quoted, yaml_field_type(key))
305
+ end
306
+
307
+ protected
308
+
309
+ def yaml_field_type key
310
+ record.class.respond_to?(:yaml_field_type) ? record.class.yaml_field_type(key) : nil
311
+ end
312
+ end
313
+
314
+ def initialize
315
+ @root = Root.new self
316
+ @handlers = [ @root ]
317
+ @anchors = {}
318
+ @filename = "<no-filename>"
319
+ end
320
+
321
+ def push_handler handler
322
+ log {"#{indent}pushing handler #{handler.class}"}
323
+ @handlers.push handler
324
+ end
325
+
326
+ def pop_handler
327
+ @handlers.pop
328
+ log {"#{indent}popped to handler #{handler.class}"}
329
+ end
330
+
331
+ # Get or set an anchor. Invoke with just the anchor name to get the value.
332
+ # Invoke with the anchor name and value to set the value.
333
+ def anchor *args
334
+ key, value, _ = args
335
+ if _
336
+ raise ArgumentError, "Expecting 1 or 2 arguments, got #{args.length}"
337
+ elsif key && value
338
+ raise "Duplicate anchor #{key}" if @anchors[key]
339
+ @anchors[key] = value
340
+ elsif key
341
+ @anchors[key]
342
+ else
343
+ nil
344
+ end
345
+ end
346
+
347
+ def result; @root.result; end
348
+
349
+ def handler; @handlers.last; end
350
+
351
+ def alias key
352
+ log {"#{indent}anchor '#{key}'=#{anchor(key)}"}
353
+ handler.alias key
354
+ end
355
+
356
+ def start_mapping *args
357
+ log {"#{indent}start mapping #{args}"}
358
+ anchor, tag, _ = args
359
+ value = handler.start_mapping tag
360
+ anchor anchor, value
361
+ end
362
+
363
+ def start_sequence *args
364
+ log {"#{indent}start sequence : #{args}"}
365
+ anchor, _ = args
366
+ value = handler.start_sequence
367
+ anchor anchor, value
368
+ end
369
+
370
+ def end_sequence
371
+ log {"#{indent}end sequence"}
372
+ handler.end_sequence
373
+ end
374
+
375
+ def end_mapping
376
+ log {"#{indent}end mapping"}
377
+ handler.end_mapping
378
+ end
379
+
380
+ def scalar *args
381
+ # value, anchor, tag, plain, quoted, style
382
+ value, anchor, tag, _, quoted = args
383
+ log {"#{indent}got scalar #{tag ? tag + '=' : ''}#{value}#{anchor ? '#' + anchor : ''}"}
384
+ value = handler.scalar value, tag, quoted
385
+ anchor anchor, value
386
+ end
387
+
388
+ def log &block
389
+ logger.debug('conjur/dsl2/handler') {
390
+ yield
391
+ }
392
+ end
393
+
394
+ def indent
395
+ " " * [ @handlers.length - 1, 0 ].max
396
+ end
397
+ end
398
+ end
399
+ end
400
+ end