conjur-asset-dsl2 0.4.4 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 233f1ca1ec584b5ac5bca8113871edaaacb3af3e
4
- data.tar.gz: 8e5dfcfdb84591716c5ae0e7d64775e22480ed48
3
+ metadata.gz: 4e808b958cc3f5faac0227b00000b8b11d5ad255
4
+ data.tar.gz: 9f4f5bb57310b929f8b7d8f3e7785e39cb8441b1
5
5
  SHA512:
6
- metadata.gz: 183166ebb0dbbcb2d5320a52ac41f799d896d2a01a53b3fd532ac885aaa8205b560e0a9b93ad0e19c95a7d826582d763de505671d457e41f68b95ceab63bd972
7
- data.tar.gz: 0f97b59ad03fafeff8a526f926a55d424b3460484a575155049649b54724f6ef88b79fd0bd515013f98d584d762463cc064f13bc07c30173681ba3d1ec260441
6
+ metadata.gz: f7f304a0432b4cb7fbea8b079c8b690c081c073833b257c8f5fb14e8b17fc3ba28f63436e33e506ec5f812ef6c6a60d64ae4e23eb5b35b22ea0ba20255846728
7
+ data.tar.gz: a52561942757c5fef6c95dfdcb83eb8c85b864e1d738337d4e72e5bcd587057610dd2d42efbd68a8f11853a911eaa8deb4a9940d5fe51ce248048c32e6f58776
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ # 0.5.0
2
+
3
+ * Refactor how the policy statements are validated and normalized, fixing some bugs in the process.
4
+
1
5
  # 0.4.4
2
6
 
3
7
  * Enable immutable attributes to be set when the value is unchanged.
data/Rakefile CHANGED
@@ -9,10 +9,8 @@ Cucumber::Rake::Task.new :features
9
9
 
10
10
  task :jenkins => ['ci:setup:rspec', :spec] do
11
11
  Cucumber::Rake::Task.new do |t|
12
- t.cucumber_opts = "--tags ~@wip --format progress --format junit --out features/reports"
12
+ t.cucumber_opts = "--tags ~@wip --format pretty --format junit --out features/reports"
13
13
  end.runner.run
14
-
15
-
16
14
  end
17
15
 
18
16
  task default: [:spec, :features]
data/ci/test.sh ADDED
@@ -0,0 +1,13 @@
1
+ #!/bin/bash -ex
2
+
3
+ echo 127.0.0.1 conjur >> /etc/hosts
4
+
5
+ /opt/conjur/evoke/bin/wait_for_conjur
6
+
7
+ cd /src/conjur-asset-dsl2
8
+ bundle
9
+
10
+ export CONJUR_AUTHN_LOGIN=admin
11
+ export CONJUR_AUTHN_API_KEY=secret
12
+
13
+ bundle exec rake jenkins || true
data/jenkins.sh CHANGED
@@ -1,36 +1,27 @@
1
- #!/bin/bash -e
2
-
3
- mkdir -p tmp
4
-
5
- function wait_for_conjur {
6
- docker pull registry.tld/wait-for-conjur
7
- docker run -i --rm --link $cid:conjur registry.tld/wait-for-conjur
8
- }
9
-
10
- PROJECT=conjur-asset-dsl2
11
- BASE_IMAGE=registry.tld/conjur-appliance-cuke-master:4.6-stable
12
- docker pull $BASE_IMAGE
13
-
14
- cid_file=tmp/$PROJECT-dev.cid
15
-
16
- docker build -t $PROJECT-dev -f Dockerfile.dev .
17
-
18
- docker run \
19
- -d \
20
- --cidfile=$cid_file \
21
- -v $PWD:/src/conjur-asset-dsl2 \
22
- $PROJECT-dev
23
-
24
- cid=$(cat $cid_file)
25
-
26
- function finish {
27
- rm -f $cid_file
28
- docker rm -f $cid
29
- }
30
-
31
- wait_for_conjur
32
-
33
-
34
- trap finish EXIT
35
-
36
- docker exec $cid bash -c "bundle exec rake jenkins" || true
1
+ #!/bin/bash -ex
2
+
3
+ CONJUR_VERSION=${CONJUR_VERSION:-"4.6"}
4
+ DOCKER_IMAGE=${DOCKER_IMAGE:-"registry.tld/conjur-appliance-cuke-master:$CONJUR_VERSION-stable"}
5
+ NOKILL=${NOKILL:-"0"}
6
+ PULL=${PULL:-"1"}
7
+
8
+ if [ -z "$CONJUR_CONTAINER" ]; then
9
+ if [ "$PULL" == "1" ]; then
10
+ docker pull $DOCKER_IMAGE
11
+ fi
12
+
13
+ cid=$(docker run -d -v ${PWD}:/src/conjur-asset-dsl2 $DOCKER_IMAGE)
14
+ function finish {
15
+ if [ "$NOKILL" != "1" ]; then
16
+ docker rm -f ${cid}
17
+ fi
18
+ }
19
+ trap finish EXIT
20
+
21
+ >&2 echo "Container id:"
22
+ >&2 echo $cid
23
+ else
24
+ cid=${CONJUR_CONTAINER}
25
+ fi
26
+
27
+ docker exec -i ${cid} /src/conjur-asset-dsl2/ci/test.sh
@@ -65,7 +65,7 @@ class Conjur::Command::DSL2 < Conjur::DSLCommand
65
65
  actions = []
66
66
  records.each do |record|
67
67
  executor_class = Conjur::DSL2::Executor.class_for(record)
68
- executor = Conjur::DSL2::Executor.class_for(record).new(record, actions, Conjur::Core::API.conjur_account)
68
+ executor = Conjur::DSL2::Executor.class_for(record).new(record, actions)
69
69
  executor.execute
70
70
  end
71
71
  Conjur::DSL2::HTTPExecutor.new(api).execute actions
@@ -157,7 +157,19 @@ command. Therefore, a policy can be loaded in three steps, if desired:
157
157
 
158
158
  filename = args.pop
159
159
  records = load filename, options[:syntax]
160
- plan = Conjur::DSL2::Planner.plan(records, api, options.slice(:namespace, :ownerid))
160
+
161
+ ownerid = options[:ownerid]
162
+ unless ownerid
163
+ user_kind, user_id = api.username.split('/', 2)
164
+ unless user_id
165
+ user_id = user_kind
166
+ user_kind = 'user'
167
+ end
168
+ ownerid = [ Conjur.configuration.account, user_kind, user_id ].join(":")
169
+ end
170
+
171
+ records = Conjur::DSL2::Resolver.resolve(records, Conjur.configuration.account, ownerid, options[:namespace])
172
+ plan = Conjur::DSL2::Planner.plan(records, api)
161
173
 
162
174
  if options[:"dry-run"]
163
175
  case options[:"format"]
@@ -6,12 +6,11 @@ module Conjur::DSL2
6
6
  class Base
7
7
  include Conjur::DSL2::Logger
8
8
 
9
- attr_reader :statement, :actions, :default_account
9
+ attr_reader :statement, :actions
10
10
 
11
- def initialize statement, actions, default_account
11
+ def initialize statement, actions
12
12
  @statement = statement
13
13
  @actions = actions
14
- @default_account = default_account
15
14
  end
16
15
 
17
16
  def action obj
@@ -24,12 +23,12 @@ module Conjur::DSL2
24
23
 
25
24
  def resource_path record = nil
26
25
  record ||= self.statement
27
- [ "authz", record.account || default_account, "resources", record.resource_kind, record.id ].join('/')
26
+ [ "authz", record.account, "resources", record.resource_kind, record.id ].join('/')
28
27
  end
29
28
 
30
29
  def role_path record = nil
31
30
  record ||= self.statement
32
- [ "authz", record.account || default_account, "roles", record.role_kind, record.id ].join('/')
31
+ [ "authz", record.account, "roles", record.role_kind, record.id ].join('/')
33
32
  end
34
33
  end
35
34
 
@@ -45,7 +44,10 @@ module Conjur::DSL2
45
44
  end
46
45
 
47
46
  def update_annotation_path
48
- [ "authz", annotate_record.account || default_account, "annotations", annotate_record.resource_kind, annotate_record.id ].join('/')
47
+ [ "authz", annotate_record.account,
48
+ "annotations",
49
+ annotate_record.resource_kind,
50
+ CGI.escape(annotate_record.id) ].join('/')
49
51
  end
50
52
  end
51
53
  end
@@ -4,10 +4,6 @@ module Conjur::DSL2::Executor
4
4
  def record
5
5
  statement.record
6
6
  end
7
-
8
- def account
9
- record.account || default_account
10
- end
11
7
  end
12
8
 
13
9
  # Generic 'create' implementation which POSTs to a resources URL.
@@ -48,7 +44,7 @@ module Conjur::DSL2::Executor
48
44
  memo
49
45
  end
50
46
  params.merge! custom_attrs
51
- params["ownerid"] = record.owner.roleid(record.owner.account || default_account) if record.owner
47
+ params["ownerid"] = record.owner.roleid if record.owner
52
48
  end
53
49
  end
54
50
  end
@@ -57,7 +53,7 @@ module Conjur::DSL2::Executor
57
53
  class CreateHostFactory < CreateRecord
58
54
  def create_parameters
59
55
  super.tap do |params|
60
- params['roleid'] = record.role.roleid(default_account)
56
+ params['roleid'] = record.role.roleid
61
57
  params['layers'] = Array(record.layers).map(&:id)
62
58
  end
63
59
  end
@@ -78,7 +74,7 @@ module Conjur::DSL2::Executor
78
74
  module ActingAs
79
75
  def acting_as_parameters
80
76
  {}.tap do |params|
81
- params["acting_as"] = record.owner.roleid(default_account) if record.owner
77
+ params["acting_as"] = record.owner.roleid if record.owner
82
78
  end
83
79
  end
84
80
  end
@@ -6,7 +6,7 @@ module Conjur::DSL2::Executor
6
6
  action({
7
7
  'method' => 'post',
8
8
  'path' => "#{resource_path(statement.resource)}?deny",
9
- 'parameters' => { "privilege" => statement.privilege, "role" => statement.role.roleid(default_account) }
9
+ 'parameters' => { "privilege" => statement.privilege, "role" => statement.role.roleid }
10
10
  })
11
11
  end
12
12
  end
@@ -5,7 +5,7 @@ module Conjur::DSL2::Executor
5
5
  action({
6
6
  'method' => 'put',
7
7
  'path' => resource_path(statement.resource),
8
- 'parameters' => { "owner" => statement.owner.roleid(default_account) }
8
+ 'parameters' => { "owner" => statement.owner.roleid }
9
9
  })
10
10
  end
11
11
  end
@@ -1,11 +1,11 @@
1
1
  module Conjur::DSL2::Executor
2
2
  class Grant < Base
3
3
  def execute
4
- parameters = { "member" => statement.member.role.roleid(default_account) }
4
+ parameters = { "member" => statement.member.role.roleid }
5
5
  parameters['admin_option'] = statement.member.admin unless statement.member.admin.nil?
6
6
  action({
7
7
  'method' => 'put',
8
- 'path' => "authz/#{statement.role.account || default_account}/roles/#{statement.role.role_kind}/#{statement.role.id}?members",
8
+ 'path' => "authz/#{statement.role.account}/roles/#{statement.role.role_kind}/#{statement.role.id}?members",
9
9
  'parameters' => parameters
10
10
  })
11
11
  end
@@ -4,7 +4,7 @@ module Conjur::DSL2::Executor
4
4
  # record.
5
5
  class Permit < Base
6
6
  def execute
7
- parameters = { "privilege" => statement.privilege, "role" => statement.role.role.roleid(default_account) }
7
+ parameters = { "privilege" => statement.privilege, "role" => statement.role.role.roleid }
8
8
  parameters['grant_option'] = statement.role.admin unless statement.role.admin.nil?
9
9
  action({
10
10
  'method' => 'post',
@@ -4,7 +4,7 @@ module Conjur::DSL2::Executor
4
4
  action({
5
5
  'method' => 'delete',
6
6
  'path' => "#{role_path(statement.role)}?members",
7
- 'parameters' => { "member" => statement.member.roleid(default_account)}
7
+ 'parameters' => { "member" => statement.member.roleid }
8
8
  })
9
9
  end
10
10
  end
@@ -36,6 +36,8 @@ module Conjur
36
36
  end
37
37
 
38
38
  class HTTPExecutor
39
+ attr_reader :api, :context
40
+
39
41
  # @param [Conjur::API] api
40
42
  def initialize api
41
43
  @api = api
@@ -90,6 +92,7 @@ module Conjur
90
92
  # $stderr.puts "#{request.method.upcase} #{request.path} #{request.body}"
91
93
  require 'base64'
92
94
  request['Authorization'] = "Token token=\"#{Base64.strict_encode64 @api.token.to_json}\""
95
+ request['X-Conjur-Privilege'] = api.privilege if api.privilege
93
96
  response = @http.request request
94
97
  # $stderr.puts response.code
95
98
  if response.code.to_i >= 300
@@ -1,46 +1,14 @@
1
1
  module Conjur
2
2
  module DSL2
3
3
  class Plan
4
- attr_reader :actions, :policy, :roles_created, :resources_created
5
- attr_accessor :namespace, :ownerid
6
-
4
+ attr_reader :actions, :roles_created, :resources_created
7
5
 
8
- def initialize namespace = nil
9
- @namespace = namespace
6
+ def initialize
10
7
  @actions = []
11
- @policy = nil
12
8
  @roles_created = Set.new
13
9
  @resources_created = Set.new
14
10
  end
15
11
 
16
- def scoped_id id
17
- id = id.id if id.respond_to?(:id)
18
-
19
- # id is nil means it should have the same id as the policy
20
- id = '' if id.nil?
21
-
22
- if id[0] == '/'
23
- id[1..-1]
24
- else
25
-
26
- tokens = []
27
- tokens.push @namespace if @namespace
28
- tokens.push @policy.id if @policy
29
-
30
- if id.start_with?(tokens.join('/') + '/')
31
- id
32
- else
33
- tokens.push id unless id.empty?
34
- tokens.join('/')
35
- end
36
- end
37
- end
38
-
39
- def policy= policy
40
- raise "Plan policy is already specified" if @policy && policy
41
- @policy = policy
42
- end
43
-
44
12
  def action a
45
13
  @actions.push a
46
14
  end
@@ -8,6 +8,7 @@ module Conjur
8
8
  attr_accessor :plan
9
9
 
10
10
  def initialize record, api
11
+ raise "Expecting Conjur::DSL2::Types::Base, got #{record.class}" unless record.is_a?(Conjur::DSL2::Types::Base)
11
12
  @record = record
12
13
  @api = api
13
14
  end
@@ -15,46 +16,26 @@ module Conjur
15
16
  def action a
16
17
  @plan.action a
17
18
  end
18
-
19
- def scoped_id id
20
- @plan.scoped_id id
21
- end
22
-
23
- def scoped_roleid record
24
- record = record.roleid(default_account) unless record.kind_of?(String)
25
- account, kind, id = record.split(':', 3)
26
- [ account, kind, scoped_id(id) ].join(":")
27
- end
28
-
29
- def scoped_resourceid record
30
- record = record.resourceid(default_account) unless record.kind_of?(String)
31
- account, kind, id = record.split(':', 3)
32
- [ account, kind, scoped_id(id) ].join(":")
33
- end
34
-
19
+
35
20
  def account
36
- (record.account rescue nil) || default_account
37
- end
38
-
39
- def default_account
40
- Conjur.configuration.account
21
+ record.account
41
22
  end
42
23
 
43
24
  def role_record fullid
44
25
  account, kind, id = fullid.split(':', 3)
45
26
  if kind == '@'
46
- Conjur::DSL2::Types::ManagedRole.build fullid, default_account
27
+ Conjur::DSL2::Types::ManagedRole.build fullid
47
28
  else
48
29
  if record_class = record_type(kind)
49
30
  record_class.new.tap do |record|
50
- record.account = account unless account == default_account
31
+ record.account = account
51
32
  unless record.is_a?(Conjur::DSL2::Types::Variable)
52
33
  record.kind = kind if record.respond_to?(:kind=)
53
34
  end
54
35
  record.id = id
55
36
  end
56
37
  else
57
- Conjur::DSL2::Types::Role.new(fullid, default_account: default_account)
38
+ Conjur::DSL2::Types::Role.new(fullid)
58
39
  end
59
40
  end
60
41
  end
@@ -70,29 +51,30 @@ module Conjur
70
51
  alias resource_record role_record
71
52
 
72
53
  def resource
73
- api.resource(scoped_resourceid record)
54
+ api.resource(record.resourceid)
74
55
  end
75
56
 
76
57
  def role
77
- api.role(scoped_roleid record)
78
- end
79
-
80
- # Sort in canonical order -- basically, a `Record` or `Create` comes before everything
81
- # else. So the base class's sort just places those before us, and anything else gets 0.
82
- def <=> other
83
- (other.kind_of?(Conjur::DSL2::Planner::ActsAsRecord) or other.kind_of?(Conjur::DSL2::Planner::Array)) ? 1 : 0
58
+ api.role(record.roleid)
84
59
  end
85
60
 
86
61
  def resource_exists? resource
87
- resource_id = resource.kind_of?(String) ? resource : scoped_resourceid(resource)
62
+ resource_id = resource.respond_to?(:resourceid) ? resource.resourceid : resource.to_s
88
63
  (plan.resources_created.include?(resource_id) || api.resource(resource_id).exists?)
89
64
  end
90
65
 
91
66
  def role_exists? role
92
- role_id = role.kind_of?(String) ? role : scoped_roleid(role)
93
- # I believe it's correct to assume manged roles exist?
94
- return true if role_id.split(':',2).last.start_with?('@')
95
-
67
+ role_id = role.respond_to?(:roleid) ? role.roleid : role.to_s
68
+
69
+ account, kind, id = role_id.split(':', 3)
70
+ if kind == "@"
71
+ # For managed role, check if the parent record will be created
72
+ role_tokens = id.split('/')
73
+ # This is the role_name
74
+ role_tokens.pop
75
+ role_kind = role_tokens.shift
76
+ role_id = [ account, role_kind, role_tokens.join('/') ].join(":")
77
+ end
96
78
  plan.roles_created.include?(role_id) || api.role(role_id).exists?
97
79
  end
98
80
 
@@ -110,11 +92,14 @@ module Conjur
110
92
  def update_record
111
93
  update = Conjur::DSL2::Types::Update.new
112
94
  update.record = record
113
- record.id = scoped_id(record)
114
95
 
115
96
  changed = false
116
97
  record.custom_attribute_names.each do |attr|
117
- existing_value = object.attributes[attr.to_s]
98
+ existing_value = if object.respond_to?(attr)
99
+ object.send(attr)
100
+ else
101
+ object.attributes[attr.to_s]
102
+ end
118
103
  new_value = record.send(attr)
119
104
  if new_value
120
105
  if new_value == existing_value
@@ -139,17 +124,17 @@ module Conjur
139
124
  end
140
125
  end
141
126
 
142
- if record.owner && resource.owner != scoped_roleid(record.owner)
127
+ if record.owner && resource.owner != record.owner.roleid
143
128
  give = Conjur::DSL2::Types::Give.new
144
- give.resource = Conjur::DSL2::Types::Resource.new(record.resourceid(default_account), default_account: default_account)
145
- give.owner = Conjur::DSL2::Types::Role.new(scoped_roleid(record.owner), default_account: default_account)
129
+ give.resource = Conjur::DSL2::Types::Resource.new(record.resourceid)
130
+ give.owner = Conjur::DSL2::Types::Role.new(record.owner.roleid)
146
131
  action give
147
132
 
148
133
  if record.role?
149
134
  grant = Conjur::DSL2::Types::Grant.new
150
- grant.role = Conjur::DSL2::Types::Role.new(record.roleid(default_account), default_account: default_account)
135
+ grant.role = Conjur::DSL2::Types::Role.new(record.roleid)
151
136
  grant.member = Conjur::DSL2::Types::Member.new
152
- grant.member.role = Conjur::DSL2::Types::Role.new(scoped_roleid(record.owner), default_account: default_account)
137
+ grant.member.role = Conjur::DSL2::Types::Role.new(record.owner.roleid)
153
138
  grant.member.admin = true
154
139
  action grant
155
140
  end
@@ -162,12 +147,6 @@ module Conjur
162
147
  def create_record
163
148
  create = Conjur::DSL2::Types::Create.new
164
149
  create.record = record
165
- record.id = scoped_id(record)
166
- if record.owner
167
- record.owner = Conjur::DSL2::Types::Role.new(scoped_roleid(record.owner), default_account: default_account)
168
- elsif plan.ownerid
169
- record.owner = Conjur::DSL2::Types::Role.new(plan.ownerid, default_account: default_account)
170
- end
171
150
 
172
151
  if record.resource?
173
152
  existing = resource.exists? ? resource.annotations : {}
@@ -182,30 +161,11 @@ module Conjur
182
161
  end
183
162
  end
184
163
 
185
- plan.roles_created.add(record.roleid(account)) if record.role?
186
- plan.resources_created.add(record.resourceid(account)) if record.resource?
164
+ plan.roles_created.add(record.roleid) if record.role?
165
+ plan.resources_created.add(record.resourceid) if record.resource?
187
166
  action create
188
167
  end
189
168
  end
190
-
191
- class Array < Base
192
- # Array sorts before everything because sanity.
193
- def <=> other
194
- -1
195
- end
196
-
197
- def do_plan
198
-
199
- planners = record.map do |item|
200
- Planner.planner_for(item, api)
201
- end.sort
202
-
203
- planners.each do |planner|
204
- planner.plan = self.plan
205
- planner.do_plan
206
- end
207
- end
208
- end
209
169
  end
210
170
  end
211
171
  end
@@ -3,7 +3,16 @@ require 'conjur/dsl2/planner/base'
3
3
  module Conjur
4
4
  module DSL2
5
5
  module Planner
6
- class Grant < Base
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
16
  # Plans a role grant.
8
17
  #
9
18
  # The Grant record can list multiple roles and members. Each member should
@@ -16,30 +25,27 @@ module Conjur
16
25
  given_admins = Set.new
17
26
  requested_grants = Hash.new { |hash, key| hash[key] = [] }
18
27
 
19
- # Check all roles / members involved
20
- (roles + members.map(&:role)).each do |role|
21
- error("role not found: #{scoped_roleid(role)} in #{plan.roles_created.to_a}") unless role_exists?(role)
22
- end
28
+ verify_roles_available roles + members.map(&:role)
23
29
 
24
30
  roles.each do |role|
25
31
  grants = begin
26
- api.role(scoped_roleid(role)).members
32
+ api.role(role.roleid).members
27
33
  rescue RestClient::ResourceNotFound
28
34
  []
29
35
  end
30
36
 
31
37
  grants.each do |grant|
32
38
  member_roleid = grant.member.roleid
33
- given_grants[scoped_roleid(role)].push [ member_roleid, grant.admin_option ]
39
+ given_grants[role.roleid].push [ member_roleid, grant.admin_option ]
34
40
  given_admins << member_roleid if grant.admin_option
35
41
  end
36
42
  members.each do |member|
37
- requested_grants[scoped_roleid(role)].push [ scoped_roleid(member.role), !!member.admin ]
43
+ requested_grants[role.roleid].push [ member.role.roleid, !!member.admin ]
38
44
  end
39
45
  end
40
46
 
41
47
  roles.each do |role|
42
- roleid = scoped_roleid(role)
48
+ roleid = role.roleid
43
49
  given = given_grants[roleid]
44
50
  requested = requested_grants[roleid]
45
51
 
@@ -55,7 +61,7 @@ module Conjur
55
61
  if record.replace
56
62
  (Set.new(given) - Set.new(requested)).each do |p|
57
63
  member, _ = p
58
- member_roleid = role_record(member).roleid(account)
64
+ member_roleid = role_record(member).roleid
59
65
  next if given_admins.member?(member_roleid)
60
66
  revoke = Conjur::DSL2::Types::Revoke.new
61
67
  revoke.role = role_record roleid
@@ -67,10 +73,33 @@ module Conjur
67
73
  end
68
74
  end
69
75
 
70
- class Revoke < Base
76
+ class Revoke < RoleAction
71
77
  def do_plan
72
- Array(record.roles).each do |role|
73
- Array(record.members).each do |member|
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
94
+ end
95
+ end
96
+
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)
102
+
74
103
  revoke = Conjur::DSL2::Types::Revoke.new
75
104
  revoke.role = role
76
105
  revoke.member = member
@@ -11,17 +11,14 @@ 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
-
15
14
  resources = Array(record.resources)
16
15
  privileges = Array(record.privilege)
17
16
  given_permissions = Hash.new { |hash, key| hash[key] = [] }
18
17
  requested_permissions = Hash.new { |hash, key| hash[key] = [] }
19
18
 
20
-
21
-
22
19
  resources.each do |resource|
23
20
  permissions = begin
24
- JSON.parse(api.resource(scoped_resourceid(resource)).get)['permissions']
21
+ JSON.parse(api.resource(resource.resourceid).get)['permissions']
25
22
  rescue RestClient::ResourceNotFound
26
23
  []
27
24
  end
@@ -34,17 +31,17 @@ module Conjur
34
31
 
35
32
  privileges.each do |privilege|
36
33
  Array(record.roles).each do |role|
37
- requested_permissions[[privilege, scoped_resourceid(resource)]].push [ scoped_roleid(role.role), !!role.admin ]
34
+ requested_permissions[[privilege, resource.resourceid]].push [ role.role.roleid, !!role.admin ]
38
35
  end
39
36
  end
40
37
  end
41
38
 
42
39
  resources.each do |resource|
43
- error("resource not found: #{resource}") unless resource_exists?(resource)
40
+ error(%Q("Resource "#{resource}" not found in [#{plan.resources_created.to_a.sort.join(', ')}])) unless resource_exists?(resource)
44
41
 
45
42
  privileges.each do |privilege|
46
43
 
47
- target = scoped_resourceid(resource)
44
+ target = resource.resourceid
48
45
  given = given_permissions[[privilege, target]]
49
46
  requested = requested_permissions[[privilege, target]]
50
47
 
@@ -4,11 +4,6 @@ module Conjur
4
4
  module DSL2
5
5
  module Planner
6
6
  module ActsAsRecord
7
- # Record objects sort before everything else
8
- def <=> other
9
- other.kind_of?(ActsAsRecord) ? 0 : -1
10
- end
11
-
12
7
  def do_plan
13
8
  if object.exists?
14
9
  update_record
@@ -38,7 +33,8 @@ module Conjur
38
33
  include ActsAsRecord
39
34
 
40
35
  def object
41
- @object ||= api.send(record.resource_kind, scoped_id(record))
36
+ raise "Cannot create a record in non-default account #{record.account}" unless record.account == Conjur.configuration.account
37
+ @object ||= api.send(record.resource_kind, record.id)
42
38
  end
43
39
  end
44
40
 
@@ -47,57 +43,27 @@ module Conjur
47
43
 
48
44
  class Policy < Base
49
45
  def do_plan
50
- role = record.role(default_account)
51
- Role.new(role, api).tap do |role|
46
+ unless record.body.nil?
47
+ error('Not expecting a body element in policy')
48
+ end
49
+
50
+ # Create the role
51
+ Role.new(record.role, api).tap do |role|
52
52
  role.plan = plan
53
53
  role.do_plan
54
54
  end
55
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
56
+ # Copy the annotations
57
+ Hash(record.annotations).each do |k,v|
58
+ record.resource.annotations ||= {}
59
+ record.resource.annotations[k] = v
64
60
  end
65
61
 
66
- Resource.new(resource, api).tap do |resource|
62
+ # Create the resource
63
+ Resource.new(record.resource, api).tap do |resource|
67
64
  resource.plan = plan
68
65
  resource.do_plan
69
66
  end
70
-
71
- planners = record.body.map do |record|
72
- Planner.planner_for(record, api)
73
- end.sort
74
-
75
- log{ "Planing policy with body #{planners.map{|p| p.class.name}}" }
76
-
77
- planners.each do |planner|
78
- planner.log{ "Planning #{planner}"}
79
- ownerid = plan.ownerid
80
- begin
81
- plan.policy = self.record
82
-
83
- # Set the ownerid to the namespace-scoped roleid of the policy
84
- ownerid = plan.policy.roleid(account)
85
- if plan.namespace
86
- account, kind, id = ownerid.split(':', 3)
87
- ownerid = [ account, kind, [ plan.namespace, id ].join("/") ].join(":")
88
- end
89
- ownerid = ownerid
90
- plan.ownerid = ownerid
91
-
92
- planner.plan = plan
93
- planner.log { "Planning policy record #{record}" }
94
- planner.do_plan
95
- planner.log { "Done" }
96
- ensure
97
- plan.policy = nil
98
- plan.ownerid = ownerid
99
- end
100
- end
101
67
  end
102
68
  end
103
69
  end
@@ -7,14 +7,10 @@ module Conjur
7
7
  module DSL2
8
8
  module Planner
9
9
  class << self
10
- def plan records, api, options = {}
11
- namespace = options[:namespace]
12
- ownerid = options[:ownerid]
13
- plan = options[:plan] || Plan.new
10
+ def plan records, api, plan = nil
11
+ plan ||= Plan.new
14
12
  plan.tap do |plan|
15
- plan.namespace = namespace if namespace
16
- plan.ownerid = ownerid if ownerid
17
- Array(records).map{ |record| planner_for(record, api) }.sort.each do |planner|
13
+ Array(records).map{ |record| planner_for(record, api) }.each do |planner|
18
14
  planner.plan = plan
19
15
  planner.log { %Q(Planning "#{planner.record} using #{planner.class}") }
20
16
  begin
@@ -0,0 +1,216 @@
1
+ module Conjur
2
+ module DSL2
3
+ class Resolver
4
+ attr_reader :account, :ownerid, :namespace
5
+
6
+ class << self
7
+ # Resolve records to the specified owner id and namespace.
8
+ def resolve records, account, ownerid, namespace = nil
9
+ resolver_classes = [ AccountResolver, IdResolver, OwnerResolver, FlattenResolver ]
10
+ resolver_classes.each do |cls|
11
+ resolver = cls.new account, ownerid, namespace
12
+ records = resolver.resolve records
13
+ end
14
+ records
15
+ end
16
+ end
17
+
18
+ # +account+ is required. It's the default account whenever no account is specified.
19
+ # +ownerid+ is required. Any records without an owner will be assigned this owner. The exception
20
+ # is records defined in a policy, which are always owned by the policy role unless an explicit owner
21
+ # is indicated (which would be rare).
22
+ # +namespace+ is optional. It's prepended to the id of every record, except for ids which begin
23
+ # with a '/' character.
24
+ def initialize account, ownerid, namespace = nil
25
+ @account = account
26
+ @ownerid = ownerid
27
+ @namespace = namespace
28
+
29
+ raise "account is required" unless account
30
+ raise "ownerid is required" unless ownerid
31
+ raise "ownerid must be fully qualified" unless ownerid.split(":", 3).length == 3
32
+ end
33
+
34
+ protected
35
+
36
+ # Traverse an Array-ish of records, calling a +handler+ method for each one.
37
+ # If a record is a Policy, then the +policy_handler+ is invoked, after the +handler+.
38
+ def traverse records, visited, handler, policy_handler = nil
39
+ Array(records).flatten.each do |record|
40
+ next unless visited.add?(id_of(record))
41
+
42
+ handler.call record, visited
43
+ policy_handler.call record, visited if policy_handler && record.is_a?(Types::Policy)
44
+ end
45
+ end
46
+
47
+ def id_of record
48
+ record.object_id
49
+ end
50
+ end
51
+
52
+ # Updates all nil +account+ fields to the default account.
53
+ class AccountResolver < Resolver
54
+ def resolve records
55
+ traverse records, Set.new, method(:resolve_account), method(:on_resolve_policy)
56
+ end
57
+
58
+ def resolve_account record, visited
59
+ if record.respond_to?(:account) && record.respond_to?(:account=) && record.account.nil?
60
+ record.account = @account
61
+ end
62
+ traverse record.referenced_records, visited, method(:resolve_account), method(:on_resolve_policy)
63
+ end
64
+
65
+ def on_resolve_policy policy, visited
66
+ traverse policy.body, visited, method(:resolve_account), method(:on_resolve_policy)
67
+ end
68
+ end
69
+
70
+ # Makes all ids absolute, by prepending the namespace (if any) and the enclosing policy (if any).
71
+ class IdResolver < Resolver
72
+ def resolve records
73
+ traverse records, Set.new, method(:resolve_id), method(:on_resolve_policy)
74
+ end
75
+
76
+ def resolve_id record, visited
77
+ if record.respond_to?(:id) && record.respond_to?(:id=)
78
+ id = record.id
79
+ if id.blank?
80
+ raise "#{record.to_s} has no id, and no namespace is available to populate it" unless namespace
81
+ record.id = namespace
82
+ elsif id[0] == '/'
83
+ record.id = id[1..-1]
84
+ else
85
+ record.id = [ namespace, id ].compact.join('/')
86
+ end
87
+ end
88
+
89
+ traverse record.referenced_records, visited, method(:resolve_id), method(:on_resolve_policy)
90
+ end
91
+
92
+ def on_resolve_policy policy, visited
93
+ saved_namespace = @namespace
94
+ @namespace = policy.id
95
+ traverse policy.body, visited, method(:resolve_id), method(:on_resolve_policy)
96
+ ensure
97
+ @namespace = saved_namespace
98
+ end
99
+ end
100
+
101
+ # Sets the owner field for any records which support it, and don't have an owner specified.
102
+ # Within a policy, the default owner is the policy role. For global records, the
103
+ # default owner is the +ownerid+ specified in the constructor.
104
+ class OwnerResolver < Resolver
105
+ def resolve records
106
+ traverse records, Set.new, method(:resolve_owner), method(:on_resolve_policy)
107
+ end
108
+
109
+ def resolve_owner record, visited
110
+ if record.respond_to?(:owner) && record.respond_to?(:owner) && record.owner.nil?
111
+ record.owner = Types::Role.new(@ownerid)
112
+ end
113
+ end
114
+
115
+ def on_resolve_policy policy, visited
116
+ saved_ownerid = @ownerid
117
+ @ownerid = [ policy.account, "policy", policy.id ].join(":")
118
+ traverse policy.body, visited, method(:resolve_owner), method(:on_resolve_policy)
119
+ ensure
120
+ @ownerid = saved_ownerid
121
+ end
122
+ end
123
+
124
+ # Flattens and sorts all records into a single list, including YAML lists and policy body.
125
+ class FlattenResolver < Resolver
126
+ def resolve records
127
+ @result = []
128
+ traverse records, Set.new, method(:resolve_record), method(:on_resolve_policy)
129
+
130
+ # Sort record creation before anything else.
131
+ # Sort record creation in dependency order (if A owns B, then A will be created before B).
132
+ # Otherwise, preserve the existing order.
133
+
134
+ @stable_index = {}
135
+ @result.each_with_index do |obj, idx|
136
+ @stable_index[obj] = idx
137
+ end
138
+ @referenced_record_index = {}
139
+ @result.each_with_index do |obj, idx|
140
+ @referenced_record_index[obj] = obj.referenced_records.select{|r| r.respond_to?(:roleid)}.map(&:roleid)
141
+ end
142
+ @result.flatten.sort do |a,b|
143
+ score = sort_score(a) - sort_score(b)
144
+ if score == 0
145
+ if a.respond_to?(:roleid) && @referenced_record_index[b].member?(a.roleid)
146
+ score = -1
147
+ elsif b.respond_to?(:roleid) && @referenced_record_index[a].member?(b.roleid)
148
+ score = 1
149
+ else
150
+ score = @stable_index[a] - @stable_index[b]
151
+ end
152
+ end
153
+ score
154
+ end
155
+ end
156
+
157
+ protected
158
+
159
+ # Select things uniquely by class and id, in this resolver.
160
+ def id_of record
161
+ if record.respond_to?(:id)
162
+ [ record.id, record.class.name ].join("@")
163
+ else
164
+ super
165
+ end
166
+ end
167
+
168
+ # Sort "Create" and "Record" objects to the front.
169
+ def sort_score record
170
+ if record.is_a?(Types::Create) || record.is_a?(Types::Record)
171
+ -1
172
+ else
173
+ 0
174
+ end
175
+ end
176
+
177
+ # Add the record to the result.
178
+ def resolve_record record, visited
179
+ @result += Array(record)
180
+ end
181
+
182
+ # Recurse on the policy body records.
183
+ def on_resolve_policy policy, visited
184
+ body = policy.body
185
+ policy.remove_instance_variable "@body"
186
+ traverse body, visited, method(:resolve_record), method(:on_resolve_policy)
187
+ end
188
+ end
189
+
190
+ # Unsets attributes that make for more verbose YAML output. This class is used to
191
+ # compact YAML expectations in test cases. It expects pre-flattened input.
192
+ #
193
+ # +account+ attributes which match the provided account are set to nil.
194
+ # +owner+ attributes which match the provided ownerid are removed.
195
+ class CompactOutputResolver < Resolver
196
+ def resolve records
197
+ traverse records, Set.new, method(:resolve_owner)
198
+ traverse records, Set.new, method(:resolve_account)
199
+ end
200
+
201
+ def resolve_account record, visited
202
+ if record.respond_to?(:account) && record.respond_to?(:account=) && record.account && record.account == self.account
203
+ record.remove_instance_variable :@account
204
+ end
205
+ traverse record.referenced_records, visited, method(:resolve_account)
206
+ end
207
+
208
+ def resolve_owner record, visited
209
+ if record.respond_to?(:owner) && record.respond_to?(:owner=) && record.owner && record.owner.roleid == self.ownerid
210
+ record.remove_instance_variable :@owner
211
+ end
212
+ traverse record.referenced_records, visited, method(:resolve_owner)
213
+ end
214
+ end
215
+ end
216
+ end
@@ -52,7 +52,6 @@ module Conjur
52
52
  # +converter+ if the +test_function+ fails, the converter is called to coerce the type.
53
53
  # It should return +nil+ if its unable to do so.
54
54
  def expect_type value, type_name, test_function, converter = nil
55
-
56
55
  if test_function.is_a?(Class)
57
56
  cls = test_function
58
57
  test_function = lambda{ value.is_a?(cls) }
@@ -274,9 +273,6 @@ module Conjur
274
273
  extend TypeChecking
275
274
  extend AttributeDefinition
276
275
 
277
- # On creation, an owner can always be specified.
278
- attr_accessor :owner
279
-
280
276
  # Stores the mapping from attribute names to Ruby class names that will be constructed
281
277
  # to populate the attribute.
282
278
  inheritable_attr :yaml_fields
@@ -303,6 +299,18 @@ module Conjur
303
299
  false
304
300
  end
305
301
 
302
+ # Gets all 'child' records.
303
+ def referenced_records
304
+ result = []
305
+ instance_variables.map do |var|
306
+ value = instance_variable_get var
307
+ Array(value).each do |val|
308
+ result.push val if val.is_a?(Conjur::DSL2::Types::Base)
309
+ end
310
+ end
311
+ result.flatten
312
+ end
313
+
306
314
  class << self
307
315
  # Hook to register the YAML type.
308
316
  def inherited cls
@@ -314,6 +322,8 @@ module Conjur
314
322
  self.name.demodulize
315
323
  end
316
324
 
325
+ alias simple_name short_name
326
+
317
327
  def register_yaml_type simple_name
318
328
  ::YAML.add_tag "!#{simple_name}", self
319
329
  end
@@ -1,7 +1,7 @@
1
1
  module Conjur::DSL2::Types
2
2
  class Create < Base
3
3
  attribute :record
4
-
4
+
5
5
  def to_s
6
6
  messages = [ "Create #{record}" ]
7
7
  if record.resource?
@@ -7,7 +7,7 @@ module Conjur
7
7
  attribute :resource, dsl_accessor: true
8
8
 
9
9
  include ResourceMemberDSL
10
-
10
+
11
11
  def to_s
12
12
  "Deny #{role} to '#{privilege}' #{resource}"
13
13
  end
@@ -14,7 +14,14 @@ module Conjur
14
14
  end
15
15
 
16
16
  def to_s
17
- "Permit #{role.role} to [#{Array(privilege).join(', ')}] on #{Array(resource).join(', ')}#{role.admin ? ' with grant option' : ''}"
17
+ if Array === role
18
+ role_string = role.map &:role
19
+ admin = false
20
+ else
21
+ role_string = role.role
22
+ admin = role.admin
23
+ end
24
+ "Permit #{role_string} to [#{Array(privilege).join(', ')}] on #{Array(resource).join(', ')}#{admin ? ' with grant option' : ''}"
18
25
  end
19
26
  end
20
27
  end
@@ -91,12 +91,23 @@ module Conjur
91
91
  include ActsAsResource
92
92
  include ActsAsRole
93
93
 
94
- def role default_account
95
- Role.new("#{self.account || default_account}:policy:#{id}", default_account: default_account)
94
+ def role
95
+ raise "account is nil" unless account
96
+ @role ||= Role.new("#{account}:policy:#{id}").tap do |role|
97
+ role.owner = Role.new(owner.roleid)
98
+ end
96
99
  end
97
100
 
98
- def resource default_account
99
- Resource.new("#{self.account || default_account}:policy:#{id}", default_account: default_account)
101
+ def resource
102
+ raise "account is nil" unless account
103
+ @resource ||= Resource.new("#{account}:policy:#{id}").tap do |resource|
104
+ resource.owner = Role.new(role.roleid)
105
+ end
106
+ end
107
+
108
+ # Body is handled specially.
109
+ def referenced_records
110
+ super - Array(@body)
100
111
  end
101
112
 
102
113
  def body &block
@@ -34,9 +34,9 @@ module Conjur
34
34
  def initialize id = nil
35
35
  self.id = id if id
36
36
  end
37
-
37
+
38
38
  def to_s
39
- "#{resource_kind.gsub('_', ' ')} '#{id}'#{account ? ' in account \'' + account + '\'': ''}"
39
+ "#{resource_kind.gsub('_', ' ')} '#{id}'#{account && account != Conjur.configuration.account ? ' in account \'' + account + '\'': ''}"
40
40
  end
41
41
 
42
42
  def resourceid default_account = nil
@@ -66,7 +66,7 @@ module Conjur
66
66
  end
67
67
 
68
68
  module ActsAsRole
69
- def roleid default_account
69
+ def roleid default_account = nil
70
70
  [ account || default_account, role_kind, id ].join(":")
71
71
  end
72
72
 
@@ -100,7 +100,7 @@ module Conjur
100
100
  end
101
101
 
102
102
  def to_s
103
- "#{kind} #{self.class.short_name.underscore} '#{id}'#{account ? ' in account \'' + account + '\'': ''}"
103
+ "#{kind} #{self.class.short_name.underscore} '#{id}'#{account && account != Conjur.configuration.account ? ' in account \'' + account + '\'': ''}"
104
104
  end
105
105
  end
106
106
 
@@ -209,13 +209,15 @@ module Conjur
209
209
  attribute :role_name, kind: :string, singular: true
210
210
 
211
211
  class << self
212
- def build fullid, default_account
212
+ def build fullid
213
213
  account, kind, id = fullid.split(':', 3)
214
214
  raise "Expecting @ for kind, got #{kind}" unless kind == "@"
215
- record_kind, record_id, role_name = id.split('/', 3)
215
+ id_tokens = id.split('/')
216
+ record_kind = id_tokens.shift
217
+ role_name = id_tokens.pop
216
218
  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
+ record.id = id_tokens.join('/')
220
+ record.account = account
219
221
  end
220
222
  self.new record, role_name
221
223
  end
@@ -1,7 +1,7 @@
1
1
  module Conjur
2
2
  module Asset
3
3
  module DSL2
4
- VERSION = "0.4.4"
4
+ VERSION = "0.5.0"
5
5
  end
6
6
  end
7
7
  end
@@ -29,5 +29,6 @@ require 'conjur/dsl2/types/policy'
29
29
  require 'conjur/dsl2/yaml/handler'
30
30
  require 'conjur/dsl2/yaml/loader'
31
31
  require 'conjur/dsl2/ruby/loader'
32
+ require 'conjur/dsl2/resolver'
32
33
  require 'conjur/dsl2/planner'
33
34
  require 'conjur/dsl2/executor'
data/policy-bug.yml ADDED
@@ -0,0 +1,7 @@
1
+ - !policy
2
+ id: test
3
+ body:
4
+ - !group
5
+ id: a
6
+ - !layer
7
+ owner: !group a
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.4.4
4
+ version: 0.5.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-15 00:00:00.000000000 Z
11
+ date: 2016-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: safe_yaml
@@ -177,7 +177,6 @@ files:
177
177
  - .rspec
178
178
  - .travis.yml
179
179
  - CHANGELOG
180
- - Dockerfile.dev
181
180
  - Gemfile
182
181
  - LICENSE.txt
183
182
  - README.md
@@ -185,6 +184,7 @@ files:
185
184
  - backup.tar
186
185
  - bin/console
187
186
  - bin/setup
187
+ - ci/test.sh
188
188
  - conjur-asset-dsl2.gemspec
189
189
  - jenkins.sh
190
190
  - lib/conjur-asset-dsl2-version.rb
@@ -208,6 +208,7 @@ files:
208
208
  - lib/conjur/dsl2/planner/grants.rb
209
209
  - lib/conjur/dsl2/planner/permissions.rb
210
210
  - lib/conjur/dsl2/planner/record.rb
211
+ - lib/conjur/dsl2/resolver.rb
211
212
  - lib/conjur/dsl2/ruby/loader.rb
212
213
  - lib/conjur/dsl2/types/base.rb
213
214
  - lib/conjur/dsl2/types/create.rb
@@ -223,6 +224,7 @@ files:
223
224
  - lib/conjur/dsl2/types/update.rb
224
225
  - lib/conjur/dsl2/yaml/handler.rb
225
226
  - lib/conjur/dsl2/yaml/loader.rb
227
+ - policy-bug.yml
226
228
  - syntax.md
227
229
  homepage: https://github.com/conjurinc/conjur-asset-dsl2
228
230
  licenses:
data/Dockerfile.dev DELETED
@@ -1,19 +0,0 @@
1
- FROM registry.tld/conjur-appliance-cuke-master:4.6-stable
2
-
3
- WORKDIR /src/conjur-asset-dsl2
4
-
5
- RUN mkdir -p /src/conjur-asset-dsl2
6
- RUN mkdir -p /src/conjur-asset-dsl2/lib
7
- RUN mkdir -p /src/conjur-asset-dsl2/tmp
8
-
9
- ADD Gemfile ./
10
- ADD conjur-asset-dsl2.gemspec ./
11
- ADD lib/conjur-asset-dsl2-version.rb ./lib/
12
- RUN bundle
13
- ADD . .
14
-
15
- ENV CONJUR_AUTHN_LOGIN admin
16
- ENV CONJUR_AUTHN_API_KEY secret
17
- ENV CONJUR_ACCOUNT cucumber
18
- ENV CONJUR_APPLIANCE_URL https://localhost/api
19
- ENV CONJUR_CERT_FILE /opt/conjur/etc/ssl/ca.pem