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,85 @@
1
+ require 'conjur/dsl2/planner/base'
2
+
3
+ module Conjur
4
+ module DSL2
5
+ module Planner
6
+ class Grant < Base
7
+ # Plans a role grant.
8
+ #
9
+ # The Grant record can list multiple roles and members. Each member should
10
+ # be granted every role. If the +replace+ option is set, then any existing
11
+ # grant on a role that is *not* given should be revoked, except for role admins.
12
+ def do_plan
13
+
14
+ roles = Array(record.roles)
15
+ members = Array(record.members)
16
+ given_grants = Hash.new { |hash, key| hash[key] = [] }
17
+ given_admins = Set.new
18
+ requested_grants = Hash.new { |hash, key| hash[key] = [] }
19
+
20
+ # Check all roles / members involved
21
+ (roles + members.map(&:role)).each do |role|
22
+ error("role not found: #{scoped_roleid(role)} in #{plan.roles_created.to_a}") unless role_exists?(role)
23
+ end
24
+
25
+ roles.each do |role|
26
+ grants = begin
27
+ api.role(scoped_roleid(role)).members
28
+ rescue RestClient::ResourceNotFound
29
+ []
30
+ end
31
+
32
+ grants.each do |grant|
33
+ member_roleid = grant.member.roleid
34
+ given_grants[scoped_roleid(role)].push [ member_roleid, grant.admin_option ]
35
+ given_admins << member_roleid if grant.admin_option
36
+ end
37
+ members.each do |member|
38
+ requested_grants[scoped_roleid(role)].push [ scoped_roleid(member.role), !!member.admin ]
39
+ end
40
+ end
41
+
42
+ roles.each do |role|
43
+ roleid = scoped_roleid(role)
44
+ given = given_grants[roleid]
45
+ requested = requested_grants[roleid]
46
+
47
+ (Set.new(requested) - Set.new(given)).each do |p|
48
+ member, admin = p
49
+ grant = Conjur::DSL2::Types::Grant.new
50
+ grant.role = role_record roleid
51
+ grant.member = Conjur::DSL2::Types::Member.new role_record(member)
52
+ grant.member.admin = true if admin
53
+ action grant
54
+ end
55
+
56
+ if record.replace
57
+ (Set.new(given) - Set.new(requested)).each do |p|
58
+ member, _ = p
59
+ member_roleid = role_record(member).roleid(account)
60
+ next if given_admins.member?(member_roleid)
61
+ revoke = Conjur::DSL2::Types::Revoke.new
62
+ revoke.role = role_record roleid
63
+ revoke.member = role_record(member)
64
+ action revoke
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ class Revoke < Base
72
+ def do_plan
73
+ Array(record.roles).each do |role|
74
+ Array(record.members).each do |member|
75
+ revoke = Conjur::DSL2::Types::Revoke.new
76
+ revoke.role = role
77
+ revoke.member = member
78
+ action revoke
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,80 @@
1
+ require 'conjur/dsl2/planner/base'
2
+ require 'set'
3
+
4
+ module Conjur
5
+ module DSL2
6
+ module Planner
7
+ # Plans a permission.
8
+ #
9
+ # The Permit record can list multiple roles, privileges, and resources. Each privilege should
10
+ # be allowed to each role on each resource. If the +replace+ option is set, then any existing
11
+ # privilege on an existing resource that is *not* given should be denied.
12
+ class Permit < Base
13
+ def do_plan
14
+
15
+ resources = Array(record.resources)
16
+ privileges = Array(record.privilege)
17
+ given_permissions = Hash.new { |hash, key| hash[key] = [] }
18
+ requested_permissions = Hash.new { |hash, key| hash[key] = [] }
19
+
20
+
21
+
22
+ resources.each do |resource|
23
+ permissions = begin
24
+ JSON.parse(api.resource(scoped_resourceid(resource)).get)['permissions']
25
+ rescue RestClient::ResourceNotFound
26
+ []
27
+ end
28
+
29
+ permissions.each do |permission|
30
+ if privileges.member?(permission['privilege'])
31
+ given_permissions[[permission['privilege'], permission['resource']]].push [ permission['role'], permission['grant_option'] ]
32
+ end
33
+ end
34
+
35
+ privileges.each do |privilege|
36
+ Array(record.roles).each do |role|
37
+ requested_permissions[[privilege, scoped_resourceid(resource)]].push [ scoped_roleid(role.role), !!role.admin ]
38
+ end
39
+ end
40
+ end
41
+
42
+ resources.each do |resource|
43
+ error("resource not found: #{resource}") unless resource_exists?(resource)
44
+
45
+ privileges.each do |privilege|
46
+
47
+ target = scoped_resourceid(resource)
48
+ given = given_permissions[[privilege, target]]
49
+ requested = requested_permissions[[privilege, target]]
50
+
51
+ (Set.new(requested) - Set.new(given)).each do |p|
52
+ role, admin = p
53
+
54
+ error("role not found: #{role}") unless role_exists?(role)
55
+
56
+ permit = Conjur::DSL2::Types::Permit.new
57
+ permit.resource = resource_record target
58
+ permit.privilege = privilege
59
+ permit.role = Conjur::DSL2::Types::Member.new role_record(role)
60
+ permit.role.admin = true if admin
61
+ action permit
62
+ end
63
+
64
+ if record.replace
65
+ (Set.new(given) - Set.new(requested)).each do |p|
66
+ role, admin = p
67
+ deny = Conjur::DSL2::Types::Deny.new
68
+ deny.resource = resource_record target
69
+ deny.privilege = privilege
70
+ deny.role = role_record(role)
71
+ action deny
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,102 @@
1
+ require 'conjur/dsl2/planner/base'
2
+
3
+ module Conjur
4
+ module DSL2
5
+ module Planner
6
+ module ActsAsRecord
7
+ # Record objects sort before everything else
8
+ def <=> other
9
+ other.kind_of?(ActsAsRecord) ? 0 : -1
10
+ end
11
+
12
+ def do_plan
13
+ if object.exists?
14
+ update_record
15
+ else
16
+ create_record
17
+ end
18
+ end
19
+
20
+ def to_s
21
+ "<#{self.class.name} #{record.to_s}>"
22
+ end
23
+ end
24
+
25
+ class Role < Base
26
+ include ActsAsRecord
27
+
28
+ alias object role
29
+ end
30
+
31
+ class Resource < Base
32
+ include ActsAsRecord
33
+
34
+ alias object resource
35
+ end
36
+
37
+ class Record < Base
38
+ include ActsAsRecord
39
+
40
+ def object
41
+ @object ||= api.send(record.resource_kind, scoped_id(record))
42
+ end
43
+ end
44
+
45
+ class Webservice < Resource
46
+ end
47
+
48
+ class Policy < Base
49
+ def do_plan
50
+ role = record.role(default_account)
51
+ Role.new(role, api).tap do |role|
52
+ role.plan = plan
53
+ role.do_plan
54
+ end
55
+
56
+ if record.body.nil?
57
+ error('missing body element in policy')
58
+ end
59
+
60
+ plan.ownerid = role.roleid(account)
61
+ resource = record.resource(default_account)
62
+ if record.annotations
63
+ resource.annotations = record.annotations
64
+ end
65
+
66
+ Resource.new(resource, api).tap do |resource|
67
+ resource.plan = plan
68
+ resource.do_plan
69
+ end
70
+
71
+ planners = record.body.map do |record|
72
+ Planner.planner_for(record, api)
73
+ end.sort
74
+
75
+ planners.each do |planner|
76
+ ownerid = plan.ownerid
77
+ begin
78
+ plan.policy = self.record
79
+
80
+ # Set the ownerid to the namespace-scoped roleid of the policy
81
+ ownerid = plan.policy.roleid(account)
82
+ if plan.namespace
83
+ account, kind, id = ownerid.split(':', 3)
84
+ ownerid = [ account, kind, [ plan.namespace, id ].join("/") ].join(":")
85
+ end
86
+ ownerid = ownerid
87
+ plan.ownerid = ownerid
88
+
89
+ planner.plan = plan
90
+ planner.trace("planning...")
91
+ planner.do_plan
92
+ planner.trace("ok!")
93
+ ensure
94
+ plan.policy = nil
95
+ plan.ownerid = ownerid
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,38 @@
1
+ require 'conjur/dsl2/plan'
2
+ require 'conjur/dsl2/planner/record'
3
+ require 'conjur/dsl2/planner/permissions'
4
+ require 'conjur/dsl2/planner/grants'
5
+
6
+ module Conjur
7
+ module DSL2
8
+ module Planner
9
+ class << self
10
+ def plan records, api, options = {}
11
+ namespace = options[:namespace]
12
+ ownerid = options[:ownerid]
13
+ Plan.new.tap do |plan|
14
+ plan.namespace = namespace if namespace
15
+ plan.ownerid = ownerid if ownerid
16
+ records.map{ |record| planner_for(record, api) }.sort.each do |planner|
17
+ planner.plan = plan
18
+ begin
19
+ planner.do_plan
20
+ ensure
21
+ planner.plan = nil
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def planner_for record, api
28
+ cls = begin
29
+ const_get record.class.name.split("::")[-1]
30
+ rescue NameError
31
+ Record
32
+ end
33
+ cls.new record, api
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,263 @@
1
+ class Object
2
+ # Dear Ruby, I wish instance variables order was stable, because if it was
3
+ # then YAML would always come out the same.
4
+ def to_yaml_properties
5
+ instance_variables.sort
6
+ end
7
+ end
8
+
9
+ module Conjur
10
+ module DSL2
11
+ module Ruby
12
+ module RecordLoader
13
+ def respond_to_missing? sym, include_all = false
14
+ super or Conjur::DSL2::Types.const_get sym.to_s.classify rescue nil
15
+ end
16
+ end
17
+
18
+ # Implement +method_missing+ to reference basic types like Group, User, Layer, etc.
19
+ # Anything from Conjur::DSL2::Types is fair game.
20
+ module RecordReferenceFactory
21
+ include RecordLoader
22
+
23
+ # The record can have a constructor with 0 or 1 arguments. If it takes 1 argument,
24
+ # it will be populated with the first +args+, if any. It's assumed to be the id.
25
+ def method_missing sym, *args, &block
26
+ kind = Conjur::DSL2::Types.const_get sym.to_s.classify rescue nil
27
+ if kind
28
+ object = kind.new(*args)
29
+ raise "#{kind.short_name} is not createable here" unless object.role? || object.resource?
30
+ handle_object object, &block
31
+ object
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def handle_object object, &block
38
+ # pass
39
+ end
40
+ end
41
+
42
+ # Construct record properties in a block and yield the record to the loader.
43
+ module RecordFactory
44
+ include RecordReferenceFactory
45
+
46
+ def handle_object object, &block
47
+ push object
48
+ do_scope object, &block
49
+ end
50
+ end
51
+
52
+ class YAMLList < Array
53
+ def tag
54
+ [ "!", self.class.name.split("::")[-1].underscore ].join
55
+ end
56
+
57
+ def encode_with coder
58
+ coder.represent_seq tag, self
59
+ end
60
+ end
61
+
62
+ module Tagless
63
+ def tag; nil; end
64
+ end
65
+
66
+ module CustomStatement
67
+ def custom_statement handler, &block
68
+ record = yield
69
+ class << record
70
+ include RecordReferenceFactory
71
+ end
72
+ push record
73
+ do_scope record, &handler
74
+ end
75
+ end
76
+
77
+ module Grants
78
+ include CustomStatement
79
+
80
+ def grant &block
81
+ custom_statement(block) do
82
+ Conjur::DSL2::Types::Grant.new
83
+ end
84
+ end
85
+
86
+ def revoke &block
87
+ custom_statement(block) do
88
+ Conjur::DSL2::Types::Revoke.new
89
+ end
90
+ end
91
+ end
92
+
93
+ module Permissions
94
+ include CustomStatement
95
+
96
+ def permit privilege, &block
97
+ custom_statement(block) do
98
+ Conjur::DSL2::Types::Permit.new(privilege)
99
+ end
100
+ end
101
+
102
+ def give &block
103
+ custom_statement(block) do
104
+ Conjur::DSL2::Types::Give.new
105
+ end
106
+ end
107
+
108
+ def retire &block
109
+ custom_statement(block) do
110
+ Conjur::DSL2::Types::Retire.new
111
+ end
112
+ end
113
+ end
114
+
115
+ # Entitlements will allow creation of any record, as well as declaration
116
+ # of permit, deny, grant and revoke.
117
+ class Entitlements < YAMLList
118
+ include Tagless
119
+ include RecordFactory
120
+ include Grants
121
+ include Permissions
122
+
123
+ def policy id=nil, &block
124
+ policy = Policy.new
125
+ policy.id(id) unless id.nil?
126
+ push policy
127
+
128
+ do_scope policy, &block
129
+ end
130
+ end
131
+
132
+ class Body < YAMLList
133
+ include RecordFactory
134
+ include Grants
135
+ include Permissions
136
+ end
137
+
138
+ # Policy includes the functionality of Entitlements, wrapped in a
139
+ # policy role, policy resource, policy id and policy version.
140
+ class Policy < Conjur::DSL2::Types::Base
141
+ include Conjur::DSL2::Types::ActsAsResource
142
+ include Conjur::DSL2::Types::ActsAsRole
143
+
144
+ def body &block
145
+ singleton :body, lambda { Body.new }, &block
146
+ @body
147
+ end
148
+
149
+ def body= body
150
+ @body = body
151
+ end
152
+
153
+ protected
154
+
155
+ def singleton id, factory, &block
156
+ object = instance_variable_get("@#{id}")
157
+ unless object
158
+ object = factory.call
159
+ class << object
160
+ include Tagless
161
+ end
162
+ instance_variable_set("@#{id}", object)
163
+ end
164
+ do_scope object, &block
165
+ end
166
+ end
167
+
168
+ module Delegation
169
+ def respond_to? sym, include_all = false
170
+ super or scope.respond_to?(sym, include_all)
171
+ end
172
+
173
+ def method_missing(sym, *args, &block)
174
+ if scope.respond_to?(sym)
175
+ scope.send sym, *args, &block
176
+ else
177
+ raise NoMethodError, "undefined method `#{sym}` for #{scope}:#{scope.class}"
178
+ end
179
+ end
180
+ end
181
+
182
+ class Loader
183
+ include Delegation
184
+
185
+ attr_reader :script, :filename
186
+
187
+ class << self
188
+ def load_file filename
189
+ load File.read(filename), filename
190
+ end
191
+
192
+ def load yaml, filename = nil
193
+ create(yaml, filename).load
194
+ end
195
+
196
+ def create(yaml, filename=nil)
197
+ new(yaml, filename)
198
+ end
199
+
200
+ end
201
+
202
+ def initialize script, filename = nil
203
+ @script = script
204
+ @filename = filename
205
+ @scope = []
206
+ end
207
+
208
+ def loader
209
+ self
210
+ end
211
+
212
+ def load root = nil
213
+ args = [ script ]
214
+ args << filename if filename
215
+ root ||= Entitlements.new
216
+ do_scope root do
217
+ instance_eval(*args)
218
+ end
219
+ root
220
+ end
221
+
222
+ def push_scope obj
223
+ @scope.push obj
224
+ end
225
+
226
+ def scope
227
+ @scope.last
228
+ end
229
+
230
+ def pop_scope
231
+ @scope.pop
232
+ end
233
+
234
+ def do_scope obj, &block
235
+ push_scope obj
236
+ class << obj
237
+ attr_accessor :loader
238
+
239
+ def do_scope obj, &block
240
+ loader.do_scope obj, &block
241
+ end
242
+
243
+ def scope
244
+ loader.scope
245
+ end
246
+
247
+ def to_yaml_properties
248
+ super - [ :"@loader" ]
249
+ end
250
+ end
251
+ obj.loader = self
252
+ begin
253
+ yield if block_given?
254
+ ensure
255
+ pop_scope
256
+ end
257
+ end
258
+ end
259
+
260
+ Psych.add_tag "!policy", Policy
261
+ end
262
+ end
263
+ end