conjur-asset-dsl2 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4e808b958cc3f5faac0227b00000b8b11d5ad255
4
- data.tar.gz: 9f4f5bb57310b929f8b7d8f3e7785e39cb8441b1
3
+ metadata.gz: 57aa36f4305e541f313b8a833623d6100929ae50
4
+ data.tar.gz: dcabd391389d702f971b3a12aee54afadf53c914
5
5
  SHA512:
6
- metadata.gz: f7f304a0432b4cb7fbea8b079c8b690c081c073833b257c8f5fb14e8b17fc3ba28f63436e33e506ec5f812ef6c6a60d64ae4e23eb5b35b22ea0ba20255846728
7
- data.tar.gz: a52561942757c5fef6c95dfdcb83eb8c85b864e1d738337d4e72e5bcd587057610dd2d42efbd68a8f11853a911eaa8deb4a9940d5fe51ce248048c32e6f58776
6
+ metadata.gz: 62e2362f4202eaa0d48b0db4c597515531c592b63abcc8118bd88c1b8ce5ee9e4354e895ca1cf43dbea79e4a823d1d8c250c1fa5b724fca284a501fdbad695fb
7
+ data.tar.gz: 08a1ec598a5d4f23e1bb27dd939f5eaddeff097bcf0d9aebaf1ca8fabf6949134827daaf656d5a27c3d384b07dc7f5bf6b9e6a579a65f264109744f0e9dddb70
@@ -1,6 +1,13 @@
1
+ # 0.6.0
2
+
3
+ * Implement the !deny statement.
4
+ * Eliminate un-necessary privilege and role revocations.
5
+
1
6
  # 0.5.0
2
7
 
3
8
  * Refactor how the policy statements are validated and normalized, fixing some bugs in the process.
9
+ * In record ids, replace the string '$namespace' with the policy namespace. This enables cross-policy
10
+ entitlements to be made more flexibly.
4
11
 
5
12
  # 0.4.4
6
13
 
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in conjur-asset-dsl2.gemspec
4
4
  gemspec
5
+
6
+ gem 'simplecov', require: false
@@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency "ci_reporter_rspec"
31
31
  spec.add_development_dependency "aruba"
32
32
  spec.add_development_dependency 'io-grab'
33
+ spec.add_development_dependency 'simplecov'
33
34
  end
@@ -1,7 +1,7 @@
1
1
  module Conjur
2
2
  module Asset
3
3
  module DSL2
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,179 @@
1
+ module Conjur
2
+ module DSL2
3
+ module Planner
4
+ # Stores the state of existing and requested grants (roles or privileges).
5
+ #
6
+ # The difference between the existing and requested grants can be used to determine
7
+ # specifically what actions should be performed in order to bring the state of the server
8
+ # into compliance with the policy.
9
+ class BaseFacts
10
+ attr_accessor :planner, :existing, :requested, :existing_with_admin_flag, :requested_with_admin_flag
11
+
12
+ # Whether to sort the grants. By default this is off; turning it on makes the output
13
+ # deterministic which is nice for testing.
14
+ cattr_accessor :sort
15
+
16
+ def initialize planner
17
+ @planner = planner
18
+ @requested = Set.new
19
+ @requested_with_admin_flag = Set.new
20
+ @existing = Set.new
21
+ @existing_with_admin_flag = Set.new
22
+ end
23
+
24
+ def api
25
+ planner.api
26
+ end
27
+
28
+ # Return the set of grants which are requested but not already held.
29
+ #
30
+ # Note that if a grant is held with a different admin option than requested,
31
+ # re-applying with the new admin option will update the grant and create
32
+ # the desired state.
33
+ def grants_to_apply
34
+ sort(requested_with_admin_flag - existing_with_admin_flag)
35
+ end
36
+
37
+ # Return the set of grants which are held but not requested.
38
+ #
39
+ # The admin flag is ignored by this method. So, if a grant exists (with or without
40
+ # admin), and it is not requested (with or without admin), it is revoked. The
41
+ # case in which the grant is held with a different admin option than requested
42
+ # is handled by +grants_to_apply+.
43
+ def grants_to_revoke
44
+ sort(existing - requested)
45
+ end
46
+
47
+ def validate_role_exists! role
48
+ error("Role not found: #{role}") unless planner.role_exists?(role)
49
+ end
50
+
51
+ def validate_resource_exists! resource
52
+ error("Resource not found: #{resource}") unless planner.resource_exists?(resource)
53
+ end
54
+
55
+ protected
56
+
57
+ # Sort a result if +sort+ is enabled.
58
+ def sort result
59
+ self.class.sort ? result.to_a.sort : result
60
+ end
61
+ end
62
+
63
+ # Role grants are a tuple of [ roleid, member_roleid, admin_option ].
64
+ class RoleFacts < BaseFacts
65
+
66
+ # Enumerate all existing grants on the specified +role+.
67
+ # Each grant is yielded to the block.
68
+ def role_grants role, &block
69
+ begin
70
+ api.role(role.roleid).members
71
+ rescue RestClient::ResourceNotFound
72
+ if api.role(role.roleid).exists?
73
+ $stderr.puts "WARNING: Unable to fetch members of role #{role.roleid}. Use 'elevate' mode, or at least 'reveal' mode, for policy management."
74
+ end
75
+ []
76
+ end.each do |grant|
77
+ yield grant
78
+ end
79
+ end
80
+
81
+ # Validate that all the requested roles exist.
82
+ def validate!
83
+ requested.to_a.flatten.uniq.each do |roleid|
84
+ validate_role_exists! roleid
85
+ end
86
+ end
87
+
88
+ # Add a Types::Grant to the set of requested grants.
89
+ def add_requested_grant grant
90
+ Array(grant.roles).each do |role|
91
+ Array(grant.members).each do |member|
92
+ requested.add [ role.roleid, member.role.roleid ]
93
+ requested_with_admin_flag.add [ role.roleid, member.role.roleid, !!member.admin ]
94
+ end
95
+ end
96
+ end
97
+
98
+ # Removes a Types::Revoke from the set of requested grants.
99
+ def remove_revoked_grant revoke
100
+ Array(revoke.roles).each do |role|
101
+ Array(revoke.members).each do |member|
102
+ requested.delete [ role.roleid, member.roleid ]
103
+ requested_with_admin_flag.delete [ role.roleid, member.roleid, true ]
104
+ requested_with_admin_flag.delete [ role.roleid, member.roleid, false ]
105
+ end
106
+ end
107
+ end
108
+
109
+ # Add a Conjur::API::Rolerevoke that is already held.
110
+ def add_existing_grant role, grant
111
+ existing.add [ role.roleid, grant.member.roleid ]
112
+ existing_with_admin_flag.add [ role.roleid, grant.member.roleid, grant.admin_option ]
113
+ end
114
+ end
115
+
116
+ # Privilege grants are [ roleid, privilege, resourceid, grant_option ].
117
+ class PrivilegeFacts < BaseFacts
118
+
119
+ # Enumerate all existing permissions for the specified +resource+.
120
+ # Only permissions that apply the specified +privilege+ are considered.
121
+ # Each permission is yielded to the block.
122
+ def resource_permissions resource, privileges, &block
123
+ permissions = begin
124
+ JSON.parse(api.resource(resource.resourceid).get)['permissions']
125
+ rescue RestClient::ResourceNotFound
126
+ if api.resource(resource.resourceid).exists?
127
+ $stderr.puts "WARNING: Unable to fetch permissions of resource #{resource.resourceid}. Use 'elevate' mode, or at least 'reveal' mode, for policy management."
128
+ end
129
+ []
130
+ end
131
+ permissions.select{|p| privileges.member?(p['privilege'])}.each do |permission|
132
+ yield permission
133
+ end
134
+ end
135
+
136
+ # Validate that all the requested roles exist.
137
+ def validate!
138
+ requested.to_a.map{|row| row[0]}.uniq.each do |roleid|
139
+ validate_role_exists! roleid
140
+ end
141
+ requested.to_a.map{|row| row[2]}.uniq.each do |resourceid|
142
+ validate_resource_exists! resourceid
143
+ end
144
+ end
145
+
146
+ # Add a Types::deny to the set of requested grants.
147
+ def add_requested_permission permit
148
+ Array(permit.roles).each do |member|
149
+ Array(permit.privileges).each do |privilege|
150
+ Array(permit.resources).each do |resource|
151
+ requested.add [ member.role.roleid, privilege, resource.resourceid ]
152
+ requested_with_admin_flag.add [ member.role.roleid, privilege, resource.resourceid, !!member.admin ]
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ # Removes a Types::Deny from the set of requested grants.
159
+ def remove_revoked_permission deny
160
+ Array(deny.roles).each do |role|
161
+ Array(deny.privileges).each do |privilege|
162
+ Array(deny.resources).each do |resource|
163
+ requested.delete [ role.roleid, privilege, resource.resourceid ]
164
+ requested_with_admin_flag.delete [ role.roleid, privilege, resource.resourceid, true ]
165
+ requested_with_admin_flag.delete [ role.roleid, privilege, resource.resourceid, false ]
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ # Add a permission that is already held.
172
+ def add_existing_permission permission
173
+ existing.add [ permission['role'], permission['privilege'], permission['resource'] ]
174
+ existing_with_admin_flag.add [ permission['role'], permission['privilege'], permission['resource'], permission['grant_option'] ]
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -1,110 +1,77 @@
1
1
  require 'conjur/dsl2/planner/base'
2
+ require 'conjur/dsl2/planner/facts'
2
3
 
3
4
  module Conjur
4
5
  module DSL2
5
6
  module Planner
6
- class RoleAction < Base
7
- def verify_roles_available roles
8
- # Check all roles / members involved
9
- roles.each do |role|
10
- error("role not found: #{role.roleid} in #{plan.roles_created.to_a}") unless role_exists?(role)
11
- end
12
- end
13
- end
14
-
15
- class Grant < RoleAction
7
+ class Grant < Base
16
8
  # Plans a role grant.
17
9
  #
18
10
  # The Grant record can list multiple roles and members. Each member should
19
11
  # be granted every role. If the +replace+ option is set, then any existing
20
12
  # grant on a role that is *not* given should be revoked, except for role admins.
21
13
  def do_plan
22
- roles = Array(record.roles)
23
- members = Array(record.members)
24
- given_grants = Hash.new { |hash, key| hash[key] = [] }
25
- given_admins = Set.new
26
- requested_grants = Hash.new { |hash, key| hash[key] = [] }
27
-
28
- verify_roles_available roles + members.map(&:role)
29
-
30
- roles.each do |role|
31
- grants = begin
32
- api.role(role.roleid).members
33
- rescue RestClient::ResourceNotFound
34
- []
35
- end
36
-
37
- grants.each do |grant|
38
- member_roleid = grant.member.roleid
39
- given_grants[role.roleid].push [ member_roleid, grant.admin_option ]
40
- given_admins << member_roleid if grant.admin_option
41
- end
42
- members.each do |member|
43
- requested_grants[role.roleid].push [ member.role.roleid, !!member.admin ]
14
+ facts = RoleFacts.new self
15
+
16
+ facts.add_requested_grant record
17
+
18
+ Array(record.roles).each do |role|
19
+ facts.role_grants(role) do |grant|
20
+ facts.add_existing_grant role, grant
44
21
  end
45
22
  end
46
23
 
47
- roles.each do |role|
48
- roleid = role.roleid
49
- given = given_grants[roleid]
50
- requested = requested_grants[roleid]
51
-
52
- (Set.new(requested) - Set.new(given)).each do |p|
53
- member, admin = p
54
- grant = Conjur::DSL2::Types::Grant.new
55
- grant.role = role_record roleid
56
- grant.member = Conjur::DSL2::Types::Member.new role_record(member)
57
- grant.member.admin = true if admin
58
- action grant
59
- end
24
+ facts.validate!
25
+
26
+ facts.grants_to_apply.each do |grant|
27
+ roleid, memberid, admin = grant
28
+ grant = Conjur::DSL2::Types::Grant.new
29
+ grant.role = role_record roleid
30
+ grant.member = Conjur::DSL2::Types::Member.new role_record(memberid)
31
+ grant.member.admin = admin
32
+ action grant
33
+ end
60
34
 
61
- if record.replace
62
- (Set.new(given) - Set.new(requested)).each do |p|
63
- member, _ = p
64
- member_roleid = role_record(member).roleid
65
- next if given_admins.member?(member_roleid)
66
- revoke = Conjur::DSL2::Types::Revoke.new
67
- revoke.role = role_record roleid
68
- revoke.member = role_record(member)
69
- action revoke
70
- end
35
+ if record.replace
36
+ facts.grants_to_revoke.each do |grant|
37
+ roleid, memberid = grant
38
+ revoke = Conjur::DSL2::Types::Revoke.new
39
+ revoke.role = role_record roleid
40
+ revoke.member = role_record(memberid)
41
+ action revoke
71
42
  end
72
43
  end
73
44
  end
74
45
  end
75
46
 
76
- class Revoke < RoleAction
47
+ class Revoke < Base
77
48
  def do_plan
78
- roles = Array(record.roles)
79
- members = Array(record.members)
80
- given_grants = Hash.new { |hash, key| hash[key] = [] }
81
-
82
- verify_roles_available roles + members
83
-
84
- roles.each do |role|
85
- grants = begin
86
- api.role(role.roleid).members
87
- rescue RestClient::ResourceNotFound
88
- []
89
- end
90
-
91
- grants.each do |grant|
92
- member_roleid = grant.member.roleid
93
- given_grants[role.roleid].push member_roleid
49
+ facts = RoleFacts.new self
50
+
51
+ # Load all the role members as both requested and existing grants.
52
+ # Then revoke the Grant record, and see what's left.
53
+ Array(record.roles).each do |role|
54
+ facts.role_grants(role) do |grant|
55
+ grant_record = Types::Grant.new
56
+ grant_record.role = Types::Role.new(role.roleid)
57
+ grant_record.member = Types::Member.new Types::Role.new(grant.member.roleid)
58
+ grant_record.member.admin = grant.admin_option
59
+ facts.add_requested_grant grant_record
60
+
61
+ facts.add_existing_grant role, grant
94
62
  end
95
63
  end
64
+
65
+ facts.remove_revoked_grant record
96
66
 
97
- roles.each do |role|
98
- roleid = role.roleid
99
- given = given_grants[roleid]
100
- members.each do |member|
101
- next unless given.member?(member.roleid)
67
+ facts.validate!
102
68
 
103
- revoke = Conjur::DSL2::Types::Revoke.new
104
- revoke.role = role
105
- revoke.member = member
106
- action revoke
107
- end
69
+ facts.grants_to_revoke.each do |grant|
70
+ roleid, memberid = grant
71
+ revoke = Conjur::DSL2::Types::Revoke.new
72
+ revoke.role = role_record roleid
73
+ revoke.member = role_record(memberid)
74
+ action revoke
108
75
  end
109
76
  end
110
77
  end
@@ -1,5 +1,5 @@
1
1
  require 'conjur/dsl2/planner/base'
2
- require 'set'
2
+ require 'conjur/dsl2/planner/facts'
3
3
 
4
4
  module Conjur
5
5
  module DSL2
@@ -11,65 +11,78 @@ module Conjur
11
11
  # privilege on an existing resource that is *not* given should be denied.
12
12
  class Permit < Base
13
13
  def do_plan
14
- resources = Array(record.resources)
15
- privileges = Array(record.privilege)
16
- given_permissions = Hash.new { |hash, key| hash[key] = [] }
17
- requested_permissions = Hash.new { |hash, key| hash[key] = [] }
18
-
19
- resources.each do |resource|
20
- permissions = begin
21
- JSON.parse(api.resource(resource.resourceid).get)['permissions']
22
- rescue RestClient::ResourceNotFound
23
- []
24
- end
25
-
26
- permissions.each do |permission|
27
- if privileges.member?(permission['privilege'])
28
- given_permissions[[permission['privilege'], permission['resource']]].push [ permission['role'], permission['grant_option'] ]
29
- end
14
+ facts = PrivilegeFacts.new self
15
+
16
+ facts.add_requested_permission record
17
+
18
+ privileges = Array(record.privileges)
19
+ Array(record.resources).each do |resource|
20
+ facts.resource_permissions(resource, privileges) do |permission|
21
+ facts.add_existing_permission permission
30
22
  end
23
+ end
24
+
25
+ facts.validate!
31
26
 
32
- privileges.each do |privilege|
33
- Array(record.roles).each do |role|
34
- requested_permissions[[privilege, resource.resourceid]].push [ role.role.roleid, !!role.admin ]
35
- end
27
+ facts.grants_to_apply.each do |grant|
28
+ role, privilege, resource, admin = grant
29
+
30
+ permit = Conjur::DSL2::Types::Permit.new
31
+ permit.resource = resource_record resource
32
+ permit.privilege = privilege
33
+ permit.role = Conjur::DSL2::Types::Member.new role_record(role)
34
+ permit.role.admin = true if admin
35
+ action permit
36
+ end
37
+
38
+ if record.replace
39
+ facts.grants_to_revoke.each do |grant|
40
+ roleid, privilege, resourceid = grant
41
+ deny = Conjur::DSL2::Types::Deny.new
42
+ deny.resource = resource_record resourceid
43
+ deny.privilege = privilege
44
+ deny.role = role_record(roleid)
45
+ action deny
36
46
  end
37
47
  end
38
-
39
- resources.each do |resource|
40
- error(%Q("Resource "#{resource}" not found in [#{plan.resources_created.to_a.sort.join(', ')}])) unless resource_exists?(resource)
41
-
42
- privileges.each do |privilege|
43
-
44
- target = resource.resourceid
45
- given = given_permissions[[privilege, target]]
46
- requested = requested_permissions[[privilege, target]]
47
-
48
- (Set.new(requested) - Set.new(given)).each do |p|
49
- role, admin = p
50
-
51
- error(%Q(Role "#{role}" not found")) unless role_exists?(role)
52
-
53
- permit = Conjur::DSL2::Types::Permit.new
54
- permit.resource = resource_record target
55
- permit.privilege = privilege
56
- permit.role = Conjur::DSL2::Types::Member.new role_record(role)
57
- permit.role.admin = true if admin
58
- action permit
59
- end
60
-
61
- if record.replace
62
- (Set.new(given) - Set.new(requested)).each do |p|
63
- role, admin = p
64
- deny = Conjur::DSL2::Types::Deny.new
65
- deny.resource = resource_record target
66
- deny.privilege = privilege
67
- deny.role = role_record(role)
68
- action deny
69
- end
70
- end
48
+ end
49
+ end
50
+
51
+ # Plans a permission denial.
52
+ #
53
+ # A Deny statement is generated if the permission is currently held. Otherwise, its a nop.
54
+ class Deny < Base
55
+ def do_plan
56
+ facts = PrivilegeFacts.new self
57
+
58
+ # Load all the permissions as both requested and existing grants.
59
+ # Then remove the Deny record, and see what's left.
60
+ privileges = Array(record.privileges)
61
+ Array(record.resources).each do |resource|
62
+ facts.resource_permissions(resource, privileges) do |permission|
63
+ permit_record = Types::Permit.new
64
+ permit_record.role = Types::Role.new(permission['role'])
65
+ permit_record.role.admin = permission['grant_option']
66
+ permit_record.privilege = permission['privilege']
67
+ permit_record.resource = Types::Resource.new(permission['resource'])
68
+ facts.add_requested_permission permit_record
69
+
70
+ facts.add_existing_permission permission
71
71
  end
72
72
  end
73
+
74
+ facts.remove_revoked_permission record
75
+
76
+ facts.validate!
77
+
78
+ facts.grants_to_revoke.each do |grant|
79
+ role, privilege, resource = grant
80
+ deny = Conjur::DSL2::Types::Deny.new
81
+ deny.resource = resource_record resource
82
+ deny.privilege = privilege
83
+ deny.role = role_record(role)
84
+ action deny
85
+ end
73
86
  end
74
87
  end
75
88
  end
@@ -69,6 +69,8 @@ module Conjur
69
69
 
70
70
  # Makes all ids absolute, by prepending the namespace (if any) and the enclosing policy (if any).
71
71
  class IdResolver < Resolver
72
+ SUBSTITUTIONS = { "$namespace" => :namespace }
73
+
72
74
  def resolve records
73
75
  traverse records, Set.new, method(:resolve_id), method(:on_resolve_policy)
74
76
  end
@@ -78,12 +80,16 @@ module Conjur
78
80
  id = record.id
79
81
  if id.blank?
80
82
  raise "#{record.to_s} has no id, and no namespace is available to populate it" unless namespace
81
- record.id = namespace
83
+ id = namespace
82
84
  elsif id[0] == '/'
83
- record.id = id[1..-1]
85
+ id = id[1..-1]
84
86
  else
85
- record.id = [ namespace, id ].compact.join('/')
87
+ id = [ namespace, id ].compact.join('/')
86
88
  end
89
+
90
+ substitute! id
91
+
92
+ record.id = id
87
93
  end
88
94
 
89
95
  traverse record.referenced_records, visited, method(:resolve_id), method(:on_resolve_policy)
@@ -96,6 +102,15 @@ module Conjur
96
102
  ensure
97
103
  @namespace = saved_namespace
98
104
  end
105
+
106
+ protected
107
+
108
+ def substitute! id
109
+ SUBSTITUTIONS.each do |k,v|
110
+ next unless value = send(v)
111
+ id.gsub! k, value
112
+ end
113
+ end
99
114
  end
100
115
 
101
116
  # Sets the owner field for any records which support it, and don't have an owner specified.
@@ -8,6 +8,10 @@ module Conjur
8
8
 
9
9
  attribute :role
10
10
  attribute :admin, kind: :boolean, singular: true
11
+
12
+ def to_s
13
+ "#{role} #{admin ? 'with' : 'without'} admin option"
14
+ end
11
15
  end
12
16
  end
13
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conjur-asset-dsl2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Gilpin
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-02-24 00:00:00.000000000 Z
11
+ date: 2016-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: safe_yaml
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - '>='
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  description:
168
182
  email:
169
183
  - kgilpin@conjur.net
@@ -176,7 +190,7 @@ files:
176
190
  - .project
177
191
  - .rspec
178
192
  - .travis.yml
179
- - CHANGELOG
193
+ - CHANGELOG.md
180
194
  - Gemfile
181
195
  - LICENSE.txt
182
196
  - README.md
@@ -205,6 +219,7 @@ files:
205
219
  - lib/conjur/dsl2/plan.rb
206
220
  - lib/conjur/dsl2/planner.rb
207
221
  - lib/conjur/dsl2/planner/base.rb
222
+ - lib/conjur/dsl2/planner/facts.rb
208
223
  - lib/conjur/dsl2/planner/grants.rb
209
224
  - lib/conjur/dsl2/planner/permissions.rb
210
225
  - lib/conjur/dsl2/planner/record.rb