conjur-api 5.3.3 → 5.3.4

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
  SHA256:
3
- metadata.gz: 4d068a6fcf42161573c1d317260549dd7e30001e1c53d9edeb36e8c8646f7db7
4
- data.tar.gz: 0ea117aee05921d67c2feef6b863fae286f5556833134f410dbab73a18cc13b9
3
+ metadata.gz: 476ea2f5b5e2a375363e03e6c4659f5a425837b5e3036f41ae5aea208c56f781
4
+ data.tar.gz: 973be7e50f9a8a86c78770125723e42460e2264ceb7d71823fbd5d4962a31195
5
5
  SHA512:
6
- metadata.gz: bda454b83559d845aad1a5b13e824937f9574420c9c14d0743fd93c56f79ac6de462648901bc3c9d7612ad6243099a659f1292d9b4fe7628865e6f6ca8dfa562
7
- data.tar.gz: a6e5d7397c882d4d43ee95eead36bf51519fa5d6ea7c754b0c2648388769b9800180df8757c1ac5d3e968738e6e92dd013ba71c08686f2a3e6f85b1700556558
6
+ metadata.gz: 5c1cb2ded26fe6dfd44992ef4a81e5e71a01551f2874c1045a66fb556da05b55268cceef5124323dfbe14c7e032da3382e0d48cf21732a443a3c52e70af53b38
7
+ data.tar.gz: 58e061632c5c072134f5d2a23dab0103d73790750c054d5f6167ee0fa239598908130fc921aa3f74b9f57a93ced1092ec6165b55ae54728b3e16114baca486f1
@@ -6,11 +6,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [5.3.4] - 2020-10-29
10
+
11
+ ### Changed
12
+ - When rotating the currently logged in user's/host's API key, we now explictily
13
+ prevent use of `resource(<own_id>).rotate_api_key` for that action as the
14
+ `Conjur::API.rotate_api_key` should be used instead for that. This change is a
15
+ downstream enforcement of the stricter key rotation requirements on the server
16
+ covered by [this](https://github.com/cyberark/conjur/security/advisories/GHSA-qhjf-g9gm-64jq)
17
+ security bulletin.
18
+ [cyberark/conjur-api-ruby#181](https://github.com/cyberark/conjur-api-ruby/issues/181)
19
+
9
20
  ## [5.3.3] - 2020-08-18
10
21
  ### Changed
11
22
  - Release process is updated to ensure that the published Ruby Gem matches a tag in this repository,
12
23
  so that consumers of this gem can always reference the correct source code included in any given version.
13
- [cyberark/conjur-api-ruby](https://github.com/cyberark/conjur-api-ruby/issues/173)
24
+ [cyberark/conjur-api-ruby#173](https://github.com/cyberark/conjur-api-ruby/issues/173)
14
25
 
15
26
  ## 5.3.2 - 2018-09-24
16
27
  ### Added
@@ -321,7 +332,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
321
332
 
322
333
  ## [2.0.0] - 2013-13-12
323
334
 
324
- [Unreleased]: https://github.com/cyberark/conjur-api-ruby/compare/v5.3.3...HEAD
335
+ [Unreleased]: https://github.com/cyberark/conjur-api-ruby/compare/v5.3.4...HEAD
336
+ [5.3.4]: https://github.com/cyberark/conjur-api-ruby/compare/v5.3.3...v5.3.4
325
337
  [5.3.3]: https://github.com/cyberark/conjur-api-ruby/compare/v5.3.1...v5.3.3
326
338
  [5.3.1]: https://github.com/cyberark/conjur-api-ruby/compare/v5.3.0...v5.3.1
327
339
  [5.3.0]: https://github.com/cyberark/conjur-api-ruby/compare/v5.1.0...v5.3.0
@@ -1,20 +1,50 @@
1
- Feature: Display Host object fields.
1
+ Feature: Host object
2
2
 
3
- Background:
3
+ Scenario: API key of a newly created host is available and valid
4
4
  Given a new host
5
-
6
- Scenario: API key of a newly created host is available and valid.
7
- Then I run the code:
5
+ Then I can run the code:
8
6
  """
9
7
  expect(@host.exists?).to be(true)
10
8
  expect(@host.api_key).to be
11
9
  Conjur::API.new_from_key(@host.login, @host.api_key).token
12
10
  """
13
11
 
14
- Scenario: API key of a a host can be rotated.
15
- Then I run the code:
12
+ # Rotation of own API key should be done via `Conjur::API.rotate_api_key()`
13
+ Scenario: Host's own API key cannot be rotated with an API key
14
+ Given a new host
15
+ Then this code should fail with "You cannot rotate your own API key via this method"
16
16
  """
17
17
  host = Conjur::API.new_from_key(@host.login, @host.api_key).resource(@host.id)
18
- api_key = host.rotate_api_key
19
- Conjur::API.new_from_key(@host.login, api_key).token
18
+ host.rotate_api_key
19
+ """
20
+
21
+ # Rotation of own API key should be done via `Conjur::API.rotate_api_key()`
22
+ Scenario: Host's own API key cannot be rotated with a token
23
+ Given a new host
24
+ Then this code should fail with "You cannot rotate your own API key via this method"
25
+ """
26
+ token = Conjur::API.new_from_key(@host.login, @host.api_key).token
27
+
28
+ host = Conjur::API.new_from_token(token).resource(@host.id)
29
+ host.rotate_api_key
30
+ """
31
+
32
+ Scenario: Delegated host's API key can be rotated with an API key
33
+ Given a new delegated host
34
+ Then I can run the code:
35
+ """
36
+ delegated_host_resource = Conjur::API.new_from_key(@host_owner.login, @host_owner_api_key).resource(@host.id)
37
+ api_key = delegated_host_resource.rotate_api_key
38
+ Conjur::API.new_from_key(delegated_host_resource.login, api_key).token
39
+ """
40
+
41
+ Scenario: Delegated host's API key can be rotated with a token
42
+ Given a new delegated host
43
+ Then I can run the code:
44
+ """
45
+ token = Conjur::API.new_from_key(@host_owner.login, @host_owner_api_key).token
46
+
47
+ delegated_host_resource = Conjur::API.new_from_token(token).resource(@host.id)
48
+ api_key = delegated_host_resource.rotate_api_key
49
+ Conjur::API.new_from_key(delegated_host_resource.login, api_key).token
20
50
  """
@@ -1,7 +1,18 @@
1
- When(/^I(?: can)? run the code:$/) do |code|
1
+ Then(/^I(?: can)? run the code:$/) do |code|
2
2
  @result = eval(code).tap do |result|
3
- if ENV['DEBUG']
4
- puts result
3
+ puts result if ENV['DEBUG']
4
+ end
5
+ end
6
+
7
+ Then(/^this code should fail with "([^"]*)"$/) do |error_msg, code|
8
+ begin
9
+ @result = eval(code)
10
+ rescue Exception => exc
11
+ if not exc.message =~ %r{#{error_msg}}
12
+ fail "'#{error_msg}' was not found in '#{exc.message}'"
5
13
  end
14
+ else
15
+ puts @result if ENV['DEBUG']
16
+ fail "The provided block did not raise an error"
6
17
  end
7
18
  end
@@ -13,6 +13,25 @@ Given(/^a new user$/) do
13
13
  expect(@user_api_key).to be
14
14
  end
15
15
 
16
+ Given(/^a new delegated user$/) do
17
+ # Create a new host that is owned by that user
18
+ step 'a new user'
19
+ @user_owner = @user
20
+ @user_owner_id = @user_id
21
+ @user_owner_api_key = @user_api_key
22
+
23
+ # Create a new user that is owned by the user created earlier
24
+ @user_id = "user-#{random_hex}"
25
+ response = $conjur.load_policy 'root', <<-POLICY
26
+ - !user
27
+ id: #{@user_id}
28
+ owner: !user #{@user_owner_id}
29
+ POLICY
30
+ @user = $conjur.resource("cucumber:user:#{@user_id}")
31
+ @user_api_key = response.created_roles["cucumber:user:#{@user_id}"]['api_key']
32
+ expect(@user_api_key).to be
33
+ end
34
+
16
35
  Given(/^a new group$/) do
17
36
  @group_id = "group-#{random_hex}"
18
37
  response = $conjur.load_policy 'root', <<-POLICY
@@ -33,3 +52,24 @@ Given(/^a new host$/) do
33
52
  @host = $conjur.resource("cucumber:host:#{@host_id}")
34
53
  @host.attributes['api_key'] = @host_api_key
35
54
  end
55
+
56
+ Given(/^a new delegated host$/) do
57
+ # Create an owner user
58
+ step 'a new user'
59
+ @host_owner = @user
60
+ @host_owner_id = @user_id
61
+ @host_owner_api_key = @user_api_key
62
+
63
+ # Create a new host that is owned by that user
64
+ @host_id = "app-#{random_hex}"
65
+ response = $conjur.load_policy 'root', <<-POLICY
66
+ - !host
67
+ id: #{@host_id}
68
+ owner: !user #{@host_owner_id}
69
+ POLICY
70
+
71
+ @host_api_key = response.created_roles["cucumber:host:#{@host_id}"]['api_key']
72
+ expect(@host_api_key).to be
73
+ @host = $conjur.resource("cucumber:host:#{@host_id}")
74
+ @host.attributes['api_key'] = @host_api_key
75
+ end
@@ -1,17 +1,58 @@
1
- Feature: Display User object fields.
1
+ Feature: User object
2
2
 
3
3
  Background:
4
- Given a new user
5
4
 
6
- Scenario: User has a uidnumber.
7
- Then I run the code:
5
+ Scenario: User has a uidnumber
6
+ Given a new user
7
+ Then I can run the code:
8
8
  """
9
9
  @user.uidnumber
10
10
  """
11
11
  Then the result should be "1000"
12
12
 
13
- Scenario: Logged-in user is the current_role.
14
- Then I run the code:
13
+ Scenario: Logged-in user is the current_role
14
+ Given a new user
15
+ Then I can run the code:
15
16
  """
16
17
  expect($conjur.current_role(Conjur.configuration.account).id.to_s).to eq("cucumber:user:admin")
17
18
  """
19
+
20
+ # Rotation of own API key should be done via `Conjur::API.rotate_api_key()`
21
+ Scenario: User's own API key cannot be rotated with an API key
22
+ Given a new user
23
+ Then this code should fail with "You cannot rotate your own API key via this method"
24
+ """
25
+ user = Conjur::API.new_from_key(@user.login, @user_api_key).resource(@user.id)
26
+ user.rotate_api_key
27
+ """
28
+
29
+ # Rotation of own API key should be done via `Conjur::API.rotate_api_key()`
30
+ Scenario: User's own API key cannot be rotated with a token
31
+ Given a new user
32
+ Then this code should fail with "You cannot rotate your own API key via this method"
33
+ """
34
+ token = Conjur::API.new_from_key(@user.login, @user_api_key).token
35
+
36
+ user = Conjur::API.new_from_token(token).resource(@user.id)
37
+ user.rotate_api_key
38
+ """
39
+
40
+ Scenario: Delegated user's API key can be rotated with an API key
41
+ Given a new delegated user
42
+ Then I can run the code:
43
+ """
44
+ delegated_user_resource = Conjur::API.new_from_key(@user_owner.login, @user_owner_api_key).resource(@user.id)
45
+ api_key = delegated_user_resource.rotate_api_key
46
+ Conjur::API.new_from_key(delegated_user_resource.login, api_key).token
47
+ """
48
+
49
+ Scenario: Delegated user's API key can be rotated with a token
50
+ Given a new delegated user
51
+ Then I can run the code:
52
+ """
53
+ token = Conjur::API.new_from_key(@user_owner.login, @user_owner_api_key).token
54
+
55
+ delegated_user_resource = Conjur::API.new_from_token(token).resource(@user.id)
56
+ api_key = delegated_user_resource.rotate_api_key
57
+ Conjur::API.new_from_key(delegated_user_resource.login, api_key).token
58
+ """
@@ -1,4 +1,4 @@
1
- # Copyright 2013-2017 Conjur Inc.
1
+ # Copyright 2013-2020 Conjur Inc.
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy of
4
4
  # this software and associated documentation files (the "Software"), to deal in
@@ -19,6 +19,6 @@
19
19
 
20
20
  module Conjur
21
21
  class API
22
- VERSION = "5.3.3"
22
+ VERSION = "5.3.4"
23
23
  end
24
24
  end
@@ -52,12 +52,16 @@ module Conjur
52
52
  # @note You will not be able to access the API key returned by this method later, so you should
53
53
  # probably hang onto it it.
54
54
  #
55
- # @note You cannot rotate your own API key with this method. To do so, use `Conjur::API.rotate_api_key`
55
+ # @note You cannot rotate your own API key with this method. To do so, use `Conjur::API.rotate_api_key`.
56
56
  #
57
57
  # @note This feature requires a Conjur appliance running version 4.6 or higher.
58
58
  #
59
59
  # @return [String] the new API key for this user.
60
60
  def rotate_api_key
61
+ if login == username
62
+ raise 'You cannot rotate your own API key via this method. To do so, use `Conjur::API.rotate_api_key`'
63
+ end
64
+
61
65
  url_for(:authn_rotate_api_key, credentials, account, id).put("").body
62
66
  end
63
67
  end
@@ -20,7 +20,7 @@ module Conjur
20
20
  class API
21
21
  include QueryString
22
22
  include BuildObject
23
-
23
+
24
24
  #@!group Resources
25
25
 
26
26
  # Find a resource by its id.
@@ -84,7 +84,7 @@ module Conjur
84
84
  def resources options = {}
85
85
  options = { host: Conjur.configuration.core_url, credentials: credentials }.merge options
86
86
  options[:account] ||= Conjur.configuration.account
87
-
87
+
88
88
  host, credentials, account, kind = options.values_at(*[:host, :credentials, :account, :kind])
89
89
  fail ArgumentError, "host and account are required" unless [host, account].all?
90
90
  %w(host credentials account kind).each do |name|
@@ -169,7 +169,7 @@ module Conjur
169
169
  def ldap_sync_policy(credentials, config_name)
170
170
  RestClient::Resource.new(Conjur.configuration.core_url, credentials)['ldap-sync']["policy?config_name=#{fully_escape(config_name)}"]
171
171
  end
172
-
172
+
173
173
  private
174
174
 
175
175
  def resource_annotations resource
@@ -123,19 +123,21 @@ module Conjur
123
123
  #
124
124
  # @return [String] the api key, or nil if this instance was created from a token.
125
125
  attr_reader :api_key
126
-
126
+
127
127
  #@!attribute [r] remote_ip
128
128
  # An optional IP address to be recorded in the audit record for any actions performed by this API instance.
129
129
  attr_reader :remote_ip
130
130
 
131
131
  # The name of the user as which this api instance is authenticated. This is available whether the api
132
- # instance was created from credentials or an authentication token.
132
+ # instance was created from credentials or an authentication token. If the instance was created from
133
+ # credentials, we will use that value directly otherwise we will attempt to extract the username from
134
+ # the token (either the old-style data field or the new-style JWT `sub` field).
133
135
  #
134
136
  # @return [String] the login of the current user.
135
137
  def username
136
- @username || token['data']
138
+ @username || token['data'] || jwt_username(token)
137
139
  end
138
-
140
+
139
141
  # @api private
140
142
  # used to delegate to host providing subclasses.
141
143
  # @return [String] the host
@@ -213,7 +215,7 @@ module Conjur
213
215
  @account = account
214
216
  @username = username
215
217
  @api_key = api_key
216
-
218
+
217
219
  update_token_born
218
220
  end
219
221
 
@@ -323,6 +325,18 @@ module Conjur
323
325
 
324
326
  private
325
327
 
328
+ # Tries to get the username (subject) from a JWT API token by examining
329
+ # its content.
330
+ #
331
+ # @return [String] of the 'sub' payload field from the JWT if present,
332
+ # otherwise return nil
333
+ def jwt_username raw_token
334
+ return nil unless raw_token
335
+ return nil unless raw_token.include? 'payload'
336
+
337
+ JSON.parse(Base64.strict_decode64(raw_token["payload"]))["sub"]
338
+ end
339
+
326
340
  # Tries to refresh the token if possible.
327
341
  #
328
342
  # @return [Hash, false] false if the token couldn't be refreshed due to
@@ -20,9 +20,9 @@ module Conjur
20
20
  include LogSource
21
21
  include BuildObject
22
22
  include Routing
23
-
23
+
24
24
  attr_reader :id, :credentials
25
-
25
+
26
26
  def initialize id, credentials
27
27
  @id = Id.new id
28
28
  @credentials = credentials
@@ -34,10 +34,18 @@ module Conjur
34
34
  }
35
35
  end
36
36
 
37
- def account; id.account; end
38
- def kind; id.kind; end
39
- def identifier; id.identifier; end
40
-
37
+ def account
38
+ id.account
39
+ end
40
+
41
+ def kind
42
+ id.kind
43
+ end
44
+
45
+ def identifier
46
+ id.identifier
47
+ end
48
+
41
49
  def username
42
50
  credentials[:username] or raise "No username found in credentials"
43
51
  end
@@ -45,6 +53,5 @@ module Conjur
45
53
  def inspect
46
54
  "<#{self.class.name} id='#{id.to_s}'>"
47
55
  end
48
-
49
56
  end
50
57
  end
@@ -60,11 +60,11 @@ describe Conjur::API do
60
60
  context "after expiration" do
61
61
  it 'it reads a new token' do
62
62
  expect(Time.parse(api.token['timestamp'])).to be_within(5.seconds).of(Time.now)
63
-
63
+
64
64
  time_travel 6.minutes
65
65
  new_token = token.merge "timestamp" => Time.now.to_s
66
66
  write_token new_token
67
-
67
+
68
68
  expect(api.token).to eq(new_token)
69
69
  end
70
70
  end
@@ -85,10 +85,10 @@ describe Conjur::API do
85
85
  it 'by refreshing' do
86
86
  allow(Conjur::API).to receive(:authenticate).with(login, api_key, account: account).and_return token
87
87
  expect(Time.parse(api.token['timestamp'])).to be_within(5.seconds).of(Time.now)
88
-
88
+
89
89
  time_travel 6.minutes
90
90
  new_token = token.merge "timestamp" => Time.now.to_s
91
-
91
+
92
92
  expect(Conjur::API).to receive(:authenticate).with(login, api_key, account: account).and_return new_token
93
93
  expect(api.token).to eq(new_token)
94
94
  end
@@ -118,7 +118,7 @@ describe Conjur::API do
118
118
  subject { super().credentials }
119
119
  it { is_expected.to eq({ headers: { authorization: "Token token=\"#{Base64.strict_encode64(token.to_json)}\"" }, username: login }) }
120
120
  end
121
-
121
+
122
122
  context "with remote_ip" do
123
123
  let(:remote_ip) { "66.0.0.1" }
124
124
  describe '#credentials' do
@@ -153,7 +153,7 @@ describe Conjur::API do
153
153
  context 'basic functioning' do
154
154
  it_behaves_like 'it can clone itself'
155
155
  end
156
-
156
+
157
157
  context "forwarded for" do
158
158
  let(:forwarded_for_header) { "66.0.0.1" }
159
159
  let(:headers) { base_headers.merge(x_forwarded_for: forwarded_for_header) }
@@ -172,6 +172,55 @@ describe Conjur::API do
172
172
  end
173
173
  end
174
174
 
175
+ describe "#username" do
176
+ let(:jwt_payload) do
177
+ 'eyJzdWIiOiJ1c2VyLTlhYjBiYmZiOWJlNjA5Yzk2ZjUyN2Y1YiIsImlhdCI6MTYwMzQ5MDA4MH0='
178
+ end
179
+
180
+ let(:jwt_header) do
181
+ 'eyJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiI2MWZjOGRiZDM4MjA4NDll' \
182
+ 'ZDI4YTZhYTAwMzFjNjM5MjkxZjJmMDQzNDVjYTU0MWI5NzUxMGQ5NjkyM2I3NDlmIn0='
183
+ end
184
+
185
+ let(:conjur_token) do
186
+ {
187
+ 'data' => 'conjur-user-1234',
188
+ 'timestamp' => Time.now.to_s
189
+ }
190
+ end
191
+
192
+ let(:jwt_token) do
193
+ {
194
+ 'protected' => jwt_header,
195
+ 'payload' => jwt_payload,
196
+ }
197
+ end
198
+
199
+ it "can correctly extract the username from old Conjur token" do
200
+ expect(Conjur::API.new_from_token(conjur_token).username).to(
201
+ eq('conjur-user-1234')
202
+ )
203
+ end
204
+
205
+ context 'when using JWT token' do
206
+ it "can correctly extract username" do
207
+ expect(Conjur::API.new_from_token(jwt_token).username).to(
208
+ eq('user-9ab0bbfb9be609c96f527f5b')
209
+ )
210
+ end
211
+
212
+ it "returns nil when JWT token has no payload field" do
213
+ no_payload_jwt_token = { 'protected' => jwt_header }
214
+ expect(Conjur::API.new_from_token(no_payload_jwt_token).username).to be_nil
215
+ end
216
+
217
+ it "returns nil when JWT token has no 'sub' field in payload" do
218
+ no_sub_token = { 'payload' => 'eyJpYXQiOjE2MDM0OTAwODB9' }
219
+ expect(Conjur::API.new_from_token(no_sub_token).username).to be_nil
220
+ end
221
+ end
222
+ end
223
+
175
224
  describe "#current_role", logged_in: true do
176
225
  context "when logged in as user" do
177
226
  let(:login) { 'joerandom' }
@@ -10,5 +10,4 @@ describe Conjur::BaseObject do
10
10
  expect(base_obj.inspect).to include("id='#{id_str}'")
11
11
  expect(base_obj.inspect).to include(Conjur::BaseObject.name)
12
12
  end
13
-
14
13
  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: 5.3.3
4
+ version: 5.3.4
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: 2020-08-19 00:00:00.000000000 Z
12
+ date: 2020-10-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rest-client