conjur-api 4.26.0 → 4.28.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: e955d64cd6ce130071bad4809bfe207dc3fb8b6c
4
- data.tar.gz: b0f1cf6e8960332a9a06ec806cbb52866bd39ecc
3
+ metadata.gz: 038dcc4d85e30d07b34ca84dda11196b3cbc8a98
4
+ data.tar.gz: 7b47721f2a9af64c512c6c2fbc5a5644aa5d98e5
5
5
  SHA512:
6
- metadata.gz: 26bf9521c171451e4820aed4636071b735e7876e5b71707377e07e36d450a842d9e627ecd69e2456d8966708b3854c3bfbf9b7a7dea515067007e9ec969862f2
7
- data.tar.gz: cff05fb02234c561bbbfbf8d7130b48a72c741c9bc80db1d540212d02d7f3334b1abc3fdf72dbe6bc60d14a703713550d7670e5ce2f9e1a3f14814a2c37bef82
6
+ metadata.gz: 9d4dc629522aa37aeea760aa16ec9d9df7694754fe5490f45530de68bcba544a9b35de834288dd4576920a92b800be71fe3a03414397be0e24bd06a954929fe0
7
+ data.tar.gz: dafee7953f111a1723a22f9cc0b2a2dacd970e6c10fad46f20458380fb14951ff7ef0e83a09027aa9e3cb20d0fc993a39e6872bfce2fc46e536879ab356f2c73
@@ -1,3 +1,22 @@
1
+ # v4.28.0
2
+
3
+ * Add `Conjur::API#ldap_sync_policy` to fetch the policy to use to
4
+ bring Conjur and the LDAP server into sync.
5
+
6
+ * Remove `Conjur::API#ldap_sync_now` and `Conjur::API#ldap_sync_jobs`
7
+
8
+ # v4.27.0
9
+
10
+ * Add `Conjur::API#resources_permitted?"
11
+
12
+ * `Conjur::API#ldap_sync_now` now accepts an options Hash which will
13
+ be passed on to the `/sync` entrypoint. The old argument list is
14
+ maintained for backwards compatibility.
15
+
16
+ * `Conjur::Api#resources` now supports `:has_annotation` for
17
+ retrieving Conjur resources that have an annotation with the given
18
+ name.
19
+
1
20
  # v4.26.0
2
21
 
3
22
  * expose admin_option in the role graph (only populated by Conjur 4.8 and later)
@@ -17,6 +17,9 @@ Feature: conjur bootstrap
17
17
  Scenario: security_admin group has the expected members
18
18
  Then expressions "$conjur.role('group:security_admin').members.map(&:member).map(&:roleid).sort.join(',')" and "'cucumber:host:conjur/authn-tv,cucumber:host:conjur/expiration,cucumber:host:conjur/ldap-sync,cucumber:host:conjur/policy-loader,cucumber:host:conjur/secrets-rotator,cucumber:user:admin'" are equal
19
19
 
20
+ Scenario: security_admin group can update public keys
21
+ Then expression "$conjur.resource('service:pubkeys-1.0/public-keys').permitted_roles('update')" includes "$conjur.group('security_admin').roleid"
22
+
20
23
  Scenario: security_admin can 'elevate' and 'reveal'
21
24
  Then expression "$conjur.resource('!:!:conjur').permitted_roles('elevate')" includes "$conjur.group('security_admin').roleid"
22
25
  Then expression "$conjur.resource('!:!:conjur').permitted_roles('reveal')" includes "$conjur.group('security_admin').roleid"
@@ -19,6 +19,6 @@
19
19
 
20
20
  module Conjur
21
21
  class API
22
- VERSION = "4.26.0"
22
+ VERSION = "4.28.0"
23
23
  end
24
24
  end
@@ -18,36 +18,74 @@
18
18
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
19
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  #
21
+ require 'conjur/ldap_sync_job'
21
22
 
22
23
  module Conjur
23
24
  class API
24
- # @!group LDAP Sync Service
25
+ # @!group LDAP Sync Service
25
26
 
26
- # Trigger a LDAP sync with a given profile.
27
-
28
- # @param [String] config_name Saved profile to run sync with
29
- # @param [Boolean] dry_run Don't actually run sync, instead just report the state of the upstream LDAP.
30
- # @param [String] format Requested MIME type of the response, either 'text/yaml' or 'application/json'
31
- # @return [Hash] a hash mapping with keys 'ok' and 'result[:actions]'
32
- def ldap_sync_now(config_name, format, dry_run)
33
- opts = credentials.dup.tap{ |h|
34
- h[:headers][:accept] = format
27
+ # Fetch a Conjur policy that will bring Conjur into sync with the
28
+ # LDAP server specified by a profile.
29
+ #
30
+ # @param [String] profile the name of the LDAP server profile
31
+ # @param [Hash] options reserved for future use
32
+ def ldap_sync_policy profile, options = {}
33
+
34
+ headers = credentials.dup.tap {|h|
35
+ h[:headers][:accept] = 'text/event-stream'
35
36
  }
36
37
 
37
- dry_run = !!dry_run
38
+ options = options.merge(:config_name => profile)
39
+ url = Conjur.configuration.appliance_url + "/ldap-sync/policy?#{options.to_query}"
38
40
 
39
- resp = RestClient::Resource.new(Conjur.configuration.appliance_url, opts)['ldap-sync']['sync'].post({
40
- config_name: config_name,
41
- dry_run: dry_run
42
- })
43
-
44
- if format == 'text/yaml'
45
- resp.body
46
- elsif format == 'application/json'
47
- JSON.parse(resp.body)
48
- end
41
+ # Even though we're using SSE to return the policy, fetch the
42
+ # whole thing at once into a single response. Retrieving it in
43
+ # chunks doesn't buy us much of anything except more complicated
44
+ # client code.
45
+ response = RestClient::Resource.new(url, headers).get
46
+
47
+ json = if response.headers[:content_type] == 'text/event-stream'
48
+ find_policy_event(response) || find_error_events(response)
49
+ else
50
+ %Q({"error": {"message": "Unexpected response from server: #{response.body}"}})
51
+ end
52
+ JSON.parse(json)
53
+ end
54
+
55
+ # @api private
56
+ # Get an LDAP sync profile.
57
+
58
+ # @param [String] profile name
59
+ # @param [Hash] options reserved
60
+ def ldap_sync_show_profile(profile, options = {})
61
+ resp = RestClient::Resource.new(Conjur.configuration.appliance_url, credentials)['ldap-sync']['config'].get(options)
62
+ JSON.parse(resp.body)
63
+ end
64
+
65
+ # @api private
66
+ # Update an LDAP sync profile.
67
+ #
68
+ # ### Note
69
+ # DO NOT use this method and the UI to update an LDAP sync profile.
70
+ #
71
+ # @param [Hash] profile a hash containing the LDAP sync configuration
72
+ # @param [Hash] options reserved
73
+ def ldap_sync_update_profile(profile, options = {})
74
+ options[:json_config] = profile.to_json
75
+ resp = RestClient::Resource.new(Conjur.configuration.appliance_url, credentials)['ldap-sync']['config'].put(options.to_json, :content_type => 'application/json')
76
+ JSON.parse(resp.body)
77
+ end
78
+
79
+ # @!endgroup
80
+
81
+ private
82
+ def find_policy_event(response)
83
+ response.body.lines.find {|l| l =~ /^data: {"policy":/}.try(:[], 6..-1)
84
+ end
85
+
86
+ def find_error_events(response)
87
+ response.body.lines.collect {|l| l.match(/^data: ({"error":.*)/).try(:[], 1)}.compact.join("\n")
49
88
  end
50
89
 
51
- # @!endgroup
52
90
  end
53
91
  end
@@ -166,5 +166,32 @@ module Conjur
166
166
  def global_privilege_permitted? privilege
167
167
  resource(GLOBAL_PRIVILEGE_RESOURCE).permitted? privilege
168
168
  end
169
+
170
+ # Check to see if the logged-in role has the specified +privilege+
171
+ # on the resources specified by +kind+ and +identifiers+.
172
+ #
173
+ # @example
174
+ # secret1 = api.create_variable 'text/plain', 'secret1', id: 'secret1', value: 'my_first_secret'
175
+ # secret2 = api.create_variable 'text/plain', 'secret2', id: 'secret2', value: 'another_secret'
176
+ # all_permitted, results = api.resources_permitted? 'variable', ['secret1', 'secret2'], 'execute'
177
+
178
+ # @param [String] kind the kind of resources to check
179
+ # @param [Array<String>] identifiers the (unqualified) identifiers of the resources
180
+ # @param [String] privilege the privilege to check for
181
+ # @return [Array] first element is a Boolean, true if all checks passed, false otherwise.
182
+ # If some checks fail, second element is the check result for each resource.
183
+ def resources_permitted? kind, identifiers, privilege
184
+ options = {
185
+ privilege: privilege,
186
+ identifiers: identifiers
187
+ }
188
+ resp = RestClient::Resource.new(Conjur::Authz::API.host, credentials)["#{Conjur.account}/resources/#{kind}?check=true"].post(options)
189
+ if resp.code == 204
190
+ [true, []]
191
+ else
192
+ [false, JSON.parse(resp.body)]
193
+ end
194
+ end
195
+
169
196
  end
170
197
  end
@@ -31,8 +31,8 @@ module Conjur
31
31
  # @param [Array<Conjur::Role, String>, String, Conjur::Role] roles role or or array of roles
32
32
  # roles whose relationships we're interested in
33
33
  # @param [Hash] options options for the request
34
- # @option options [Boolean] :ancestors Whether to return ancestors of the given roles (true by default)
35
- # @option options [Boolean] :descendants Whether to return descendants of the given roles (true by default)
34
+ # @option options [Boolean] :ancestors Whether to return ancestors ("roles that your role has") of the given roles (true by default)
35
+ # @option options [Boolean] :descendants Whether to return descendants ("roles that have your role") of the given roles (true by default)
36
36
  # @option options [Conjur::Role, String] :as_role Only roles visible to this role will be included in the graph
37
37
  # @return [Conjur::Graph] An object representing the role memberships digraph
38
38
  def role_graph roles, options = {}
@@ -84,6 +84,7 @@ module Conjur
84
84
  class Pubkeys < Base
85
85
  def perform
86
86
  find_or_create_record key_managers, security_admin
87
+
87
88
  find_or_create_record pubkeys_layer, security_admin
88
89
  find_or_create_record pubkeys_host, security_admin do |record, options|
89
90
  api.create_host(id: record.id, ownerid: security_admin.roleid)
@@ -91,7 +92,11 @@ module Conjur
91
92
  pubkeys_layer.add_host pubkeys_host unless pubkeys_layer.hosts.map(&:roleid).member?(pubkeys_host.roleid)
92
93
 
93
94
  find_or_create_resource pubkeys_service, security_admin
95
+
94
96
  permit pubkeys_service, 'update', key_managers
97
+
98
+ # also permit security_admin to update public keys
99
+ permit pubkeys_service, 'update', security_admin
95
100
  end
96
101
 
97
102
  def pubkeys_layer
@@ -0,0 +1,89 @@
1
+ require 'conjur/event_source'
2
+
3
+ module Conjur
4
+ class LdapSyncJob
5
+
6
+ attr_reader :hash
7
+
8
+ # Creates a new `LdapSyncJob` from a Hash as returned
9
+ # by the LDAP sync service's `GET /jobs` route.
10
+ def self.new_from_json api, hash
11
+ new(api, hash)
12
+ end
13
+
14
+ def initialize api, hash
15
+ @api = api
16
+ @hash = hash.with_indifferent_access
17
+ end
18
+
19
+ def exclusive?
20
+ self.exclusive
21
+ end
22
+
23
+ def [](k)
24
+ @hash[k]
25
+ end
26
+
27
+ def method_missing(sym, *arguments, &block)
28
+ @hash[sym]
29
+ end
30
+
31
+ # Stop this job (if running) and remove it from the list of jobs.
32
+ def delete
33
+ job_resource.delete
34
+ end
35
+
36
+ # Receive output from this job and pass them to the given block.
37
+ def output &block
38
+ events = []
39
+ wrapper = lambda do |e|
40
+ events << e
41
+ block[e] if block
42
+ end
43
+
44
+ follow_job_output(&wrapper)
45
+
46
+ events
47
+ end
48
+
49
+ def to_s
50
+ "<LdapSyncJob #{id} type=#{type} state=#{state}#{exclusive? ? ' exclusive' : ''}>"
51
+ end
52
+
53
+ def to_h
54
+ @hash
55
+ end
56
+
57
+ alias as_json to_h
58
+
59
+ def to_json _unused
60
+ as_json.to_json
61
+ end
62
+ private
63
+
64
+ def follow_job_output &block
65
+ options = @api.credentials.dup.tap{|h| h[:headers][:accept] = 'text/event-stream'}
66
+
67
+ handle_response = lambda do |response|
68
+ response.error! unless response.code == '200'
69
+ es = EventSource.new
70
+ es.message{ |e| block[e.data] }
71
+
72
+ response.read_body do |chunk|
73
+ es.feed chunk
74
+ end
75
+ end
76
+
77
+ RestClient::Request.execute(
78
+ url: "#{job_resource['output'].url}",
79
+ headers: options[:headers],
80
+ method: :get,
81
+ block_response: handle_response
82
+ )
83
+ end
84
+
85
+ def job_resource
86
+ RestClient::Resource.new(Conjur.configuration.appliance_url, @api.credentials)['ldap-sync']['jobs'][id]
87
+ end
88
+ end
89
+ end
@@ -282,7 +282,7 @@ module Conjur
282
282
 
283
283
  path = "#{account}/resources"
284
284
  path += "/#{kind}" if kind
285
- query = opts.slice(:acting_as, :limit, :offset, :search)
285
+ query = opts.slice(:acting_as, :limit, :offset, :search, :has_annotation)
286
286
  path += "?#{query.to_query}" unless query.empty?
287
287
  resource = RestClient::Resource.new(host, credentials)[path]
288
288
 
@@ -76,8 +76,7 @@ module Conjur
76
76
  # so if `a` is a member of `b`, and `b` is a member of `c`, `a.all` will include `c`.
77
77
  #
78
78
  # ### Permissions
79
- # You must be a member of the role to call this method (note that the `admin` user is
80
- # a member of every role).
79
+ # You must be a member of the role to call this method.
81
80
  #
82
81
  # You can restrict the roles returned to one or more role ids. This feature is mainly useful
83
82
  # for checking whether this role is a member of any of a set of roles.
@@ -92,7 +91,7 @@ module Conjur
92
91
  # # => ["conjur:group:pubkeys-1.0/admin", "conjur:user:alice"]
93
92
  #
94
93
  # @example See if role `"conjur:user:alice"` is a member of either `"conjur:groups:developers"` or `"conjur:group:ops"`
95
- # is_member = not api.role('conjur:user:alice').all(filter: ['conjur:group:developers', 'conjur:group:ops']).empty?
94
+ # is_member = api.role('conjur:user:alice').all(filter: ['conjur:group:developers', 'conjur:group:ops']).any?
96
95
  #
97
96
  # @param [Hash] options options for the request
98
97
  # @option options [String, #roleid, Array<String, #roleid>] :filter only return roles in this list
@@ -12,64 +12,33 @@ require 'helpers/request_helpers'
12
12
 
13
13
  describe Conjur::API, api: :dummy do
14
14
  include RequestHelpers
15
-
16
- let(:appliance_url){ "http://example.com/api" }
17
- let(:ldapsync_url){ "#{appliance_url}/ldap-sync/sync" }
18
- let(:response_json){
19
- {
20
- :okay => true,
21
- :result => {
22
- :actions => [
23
- "Create user 'Guest'\n Set annotation 'ldap-sync/source'\n Set annotation 'ldap-sync/upstream-dn'"
24
- ]
25
- }
26
- }
27
- }
28
- let(:response){ double('response', body: response_json.to_json) }
29
- let(:dry_run) { true }
30
-
31
- before do
32
- allow(Conjur.configuration).to receive(:appliance_url).and_return appliance_url
33
- allow(Conjur::API).to receive_messages(ldap_sync_now: ldapsync_url)
34
- expect_request(
35
- url: ldapsync_url,
36
- method: :post,
37
- headers: credentials[:headers],
38
- payload: {config_name: 'default', dry_run: dry_run}
39
- ).and_return response
40
- end
41
-
42
- describe "#ldap_sync_now" do
43
- it "POSTs /sync" do
44
- api.ldap_sync_now('default', 'application/json', true)
15
+ describe 'LDAP policy methods' do
16
+ let(:appliance_url){ "http://example.com/api" }
17
+ before do
18
+ allow(Conjur.configuration).to receive(:appliance_url).and_return appliance_url
45
19
  end
46
- end
47
20
 
48
- describe "#ldap_sync_now" do
49
- let(:dry_run) { true }
50
- it "POSTs /sync with dry_run set to true" do
51
- api.ldap_sync_now('default', 'application/json', true)
21
+ describe '#ldap_sync_policy' do
22
+ let(:profile) { 'default' }
23
+ let(:url){ "#{appliance_url}/ldap-sync/policy?config_name=#{profile}" }
24
+ let(:policy_event){
25
+ %Q{data: {"policy": "a policy"}}
26
+ }
27
+
28
+ let(:response){ double('response', :body => policy_event, :headers => { :content_type => 'text/event-stream' }) }
29
+ subject{ api.ldap_sync_policy('default') }
30
+ before do
31
+ expect_request(
32
+ url: url,
33
+ method: :get,
34
+ headers: credentials[:headers]
35
+ ).and_return response
36
+ end
37
+
38
+ it 'returns a Hash with a policy' do
39
+ expect(subject).to be_kind_of Hash
40
+ end
52
41
  end
53
- end
54
42
 
55
- describe "#ldap_sync_now" do
56
- let(:dry_run) { false }
57
- it "POSTs /sync with dry_run set to false" do
58
- api.ldap_sync_now('default', 'application/json', false)
59
- end
60
- end
61
-
62
- describe "#ldap_sync_now" do
63
- let(:dry_run) { true }
64
- it "POSTs /sync with truthy dry_run value" do
65
- api.ldap_sync_now('default', 'application/json', 1)
66
- end
67
- end
68
-
69
- describe "#ldap_sync_now" do
70
- let(:dry_run) { false }
71
- it "POSTs /sync with falsey dry_run value" do
72
- api.ldap_sync_now('default', 'application/json', nil)
73
- end
74
43
  end
75
44
  end
@@ -44,4 +44,40 @@ describe Conjur::API, api: :dummy do
44
44
  expect(api.resources(kind: :chunky).map(&:url)).to eql(ids.map { |id| api.resource(id).url })
45
45
  end
46
46
  end
47
+
48
+ describe '#resources_permitted' do
49
+ let(:ids) { %w(foo bar baz) }
50
+ let(:kind) { 'variable' }
51
+ let(:priv) { 'execute' }
52
+
53
+ it 'creates the request correctly' do
54
+ expect_request(
55
+ method: :post,
56
+ url: "#{authz_host}/the-account/resources/#{kind}?check=true",
57
+ payload: {
58
+ :privilege => priv,
59
+ :identifiers => ids
60
+ }
61
+ ).and_return(double("response", :code => 204))
62
+
63
+ res = api.resources_permitted?(kind, ids, priv)
64
+ expect(res[0]).to be(true)
65
+ end
66
+
67
+ it 'signals failure' do
68
+ expect_request(
69
+ method: :post,
70
+ url: "#{authz_host}/the-account/resources/#{kind}?check=true",
71
+ payload: {
72
+ :privilege => priv,
73
+ :identifiers => ids
74
+ }
75
+ ).and_return(double("response", :code => 403, :body => '[]'))
76
+
77
+ res = api.resources_permitted?(kind, ids, priv)
78
+ expect(res[0]).to be(false)
79
+ end
80
+
81
+ end
82
+
47
83
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conjur-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.26.0
4
+ version: 4.28.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafal Rzepecki
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-07-01 00:00:00.000000000 Z
12
+ date: 2016-11-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client
@@ -383,6 +383,7 @@ files:
383
383
  - lib/conjur/host_factory_token.rb
384
384
  - lib/conjur/layer-api.rb
385
385
  - lib/conjur/layer.rb
386
+ - lib/conjur/ldap_sync_job.rb
386
387
  - lib/conjur/log.rb
387
388
  - lib/conjur/log_source.rb
388
389
  - lib/conjur/path_based.rb