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,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