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