conjur-asset-dsl2 0.4.4 → 0.5.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: 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