conjur-api 5.0.0 → 5.1.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +6 -0
  4. data/Dockerfile +2 -0
  5. data/Jenkinsfile +2 -8
  6. data/README.md +85 -2
  7. data/Rakefile +9 -3
  8. data/ci/configure_v4.sh +12 -0
  9. data/ci/configure_v5.sh +14 -0
  10. data/conjur-api.gemspec +1 -1
  11. data/docker-compose.yml +47 -12
  12. data/example/demo_v4.rb +49 -0
  13. data/example/demo_v5.rb +57 -0
  14. data/features/authn_local.feature +32 -0
  15. data/features/support/env.rb +1 -0
  16. data/features/variable_value.feature +6 -13
  17. data/features_v4/authn_local.feature +27 -0
  18. data/features_v4/exists.feature +29 -0
  19. data/features_v4/host.feature +18 -0
  20. data/features_v4/host_factory_token.feature +49 -0
  21. data/features_v4/members.feature +39 -0
  22. data/features_v4/permitted.feature +15 -0
  23. data/features_v4/permitted_roles.feature +8 -0
  24. data/features_v4/resource_fields.feature +47 -0
  25. data/features_v4/rotate_api_key.feature +13 -0
  26. data/features_v4/step_definitions/api_steps.rb +17 -0
  27. data/features_v4/step_definitions/result_steps.rb +3 -0
  28. data/features_v4/support/env.rb +23 -0
  29. data/features_v4/support/policy.yml +34 -0
  30. data/features_v4/support/world.rb +12 -0
  31. data/features_v4/variable_fields.feature +11 -0
  32. data/features_v4/variable_value.feature +54 -0
  33. data/lib/conjur-api/version.rb +1 -1
  34. data/lib/conjur/acts_as_resource.rb +3 -17
  35. data/lib/conjur/acts_as_role.rb +2 -4
  36. data/lib/conjur/acts_as_user.rb +1 -2
  37. data/lib/conjur/api.rb +1 -0
  38. data/lib/conjur/api/authn.rb +22 -8
  39. data/lib/conjur/api/host_factories.rb +2 -5
  40. data/lib/conjur/api/policies.rb +1 -1
  41. data/lib/conjur/api/pubkeys.rb +1 -9
  42. data/lib/conjur/api/resources.rb +1 -6
  43. data/lib/conjur/api/router/v4.rb +149 -0
  44. data/lib/conjur/api/router/v5.rb +150 -0
  45. data/lib/conjur/api/variables.rb +2 -8
  46. data/lib/conjur/base.rb +61 -18
  47. data/lib/conjur/base_object.rb +1 -6
  48. data/lib/conjur/configuration.rb +26 -0
  49. data/lib/conjur/group.rb +7 -1
  50. data/lib/conjur/has_attributes.rb +11 -3
  51. data/lib/conjur/host_factory.rb +1 -1
  52. data/lib/conjur/routing.rb +29 -0
  53. data/lib/conjur/user.rb +7 -1
  54. data/lib/conjur/variable.rb +26 -11
  55. data/spec/has_attributes_spec.rb +4 -2
  56. data/test.sh +25 -11
  57. metadata +33 -12
  58. data/ci/wait_for_server.sh +0 -10
  59. data/dev/docker-compose.yml +0 -23
  60. data/dev/empty.yml +0 -2
  61. data/dev/start.sh +0 -15
  62. data/dev/stop.sh +0 -6
@@ -50,14 +50,8 @@ module Conjur
50
50
  def variable_values variable_ids
51
51
  raise ArgumentError, "Variables list must be an array" unless variable_ids.kind_of? Array
52
52
  raise ArgumentError, "Variables list is empty" if variable_ids.empty?
53
-
54
- opts = "?variable_ids=#{variable_ids.map { |v| fully_escape(v) }.join(',')}"
55
-
56
- response =
57
- RestClient::Resource.
58
- new(Conjur.configuration.core_url,credentials)['secrets'+opts].get
59
-
60
- return JSON.parse(response.body)
53
+
54
+ JSON.parse(url_for(:secrets_values, credentials, variable_ids).get.body)
61
55
  end
62
56
 
63
57
  #@!endgroup
@@ -37,6 +37,8 @@ module Conjur
37
37
  class API
38
38
  include Escape
39
39
  include LogSource
40
+ include Routing
41
+ extend Routing
40
42
 
41
43
  class << self
42
44
  # Create a new {Conjur::API} instance from a username and a password or api key.
@@ -55,7 +57,7 @@ module Conjur
55
57
  # @param [String] remote_ip the optional IP address to be recorded in the audit record.
56
58
  # @param [String] account The organization account.
57
59
  # @return [Conjur::API] an api that will authenticate with the given username and api key.
58
- def new_from_key username, api_key, remote_ip: nil, account: Conjur.configuration.account
60
+ def new_from_key username, api_key, account: Conjur.configuration.account, remote_ip: nil
59
61
  self.new.init_from_key username, api_key, remote_ip: remote_ip, account: account
60
62
  end
61
63
 
@@ -101,6 +103,19 @@ module Conjur
101
103
  def new_from_token_file token_file, remote_ip: nil
102
104
  self.new.init_from_token_file token_file, remote_ip: remote_ip
103
105
  end
106
+
107
+ # Create a new {Conjur::API} instance which authenticates using +authn-local+
108
+ # using the specified username.
109
+ #
110
+ # @param [String] username the username to use when making authenticated requests.
111
+ # @param [String] account The organization account.
112
+ # @param [String] remote_ip the optional IP address to be recorded in the audit record.
113
+ # @param [String] expiration the optional expiration time of the token (supported in V5 only).
114
+ # @param [String] cidr the optional CIDR restriction on the token (supported in V5 only).
115
+ # @return [Conjur::API] an api that will authenticate with the given username.
116
+ def new_from_authn_local username, account: Conjur.configuration.account, remote_ip: nil, expiration: nil, cidr: nil
117
+ self.new.init_from_authn_local username, account: account, remote_ip: remote_ip, expiration: expiration, cidr: cidr
118
+ end
104
119
  end
105
120
 
106
121
  #@!attribute [r] api_key
@@ -139,6 +154,12 @@ module Conjur
139
154
  return @token
140
155
  end
141
156
 
157
+ # @api private
158
+ # Force the API to obtain a new access token on the next invocation.
159
+ def force_token_refresh
160
+ @token = nil
161
+ end
162
+
142
163
  # Credentials that can be merged with options to be passed to `RestClient::Resource` HTTP request methods.
143
164
  # These include a username and an Authorization header containing the authentication token.
144
165
  #
@@ -152,17 +173,7 @@ module Conjur
152
173
  { headers: headers, username: username }
153
174
  end
154
175
 
155
- module MonotonicTime
156
- def monotonic_time
157
- Process.clock_gettime Process::CLOCK_MONOTONIC
158
- rescue
159
- # fall back to normal clock if there's no CLOCK_MONOTONIC
160
- Time.now.to_f
161
- end
162
- end
163
-
164
176
  module TokenExpiration
165
- include MonotonicTime
166
177
 
167
178
  # The four minutes is to work around a bug in Conjur < 4.7 causing a 404 on
168
179
  # long-running operations (when the token is used right around the 5 minute mark).
@@ -174,9 +185,20 @@ module Conjur
174
185
  token_age > TOKEN_STALE
175
186
  end
176
187
 
188
+ def update_token_born
189
+ self.token_born = gettime
190
+ end
191
+
177
192
  def token_age
178
193
  gettime - token_born
179
194
  end
195
+
196
+ def gettime
197
+ Process.clock_gettime Process::CLOCK_MONOTONIC
198
+ rescue
199
+ # fall back to normal clock if there's no CLOCK_MONOTONIC
200
+ Time.now.to_f
201
+ end
180
202
  end
181
203
 
182
204
  # When the API is constructed with an API key, the token can be refreshed using
@@ -191,6 +213,7 @@ module Conjur
191
213
  @account = account
192
214
  @username = username
193
215
  @api_key = api_key
216
+
194
217
  update_token_born
195
218
  end
196
219
 
@@ -199,20 +222,32 @@ module Conjur
199
222
  update_token_born
200
223
  end
201
224
  end
225
+ end
202
226
 
203
- def update_token_born
204
- self.token_born = gettime
227
+ # Obtains access tokens from the +authn-local+ service.
228
+ class LocalAuthenticator
229
+ include TokenExpiration
230
+
231
+ attr_reader :account, :username, :expiration, :cidr
232
+
233
+ def initialize account, username, expiration, cidr
234
+ @account = account
235
+ @username = username
236
+ @expiration = expiration
237
+ @cidr = cidr
238
+
239
+ update_token_born
205
240
  end
206
241
 
207
- def gettime
208
- monotonic_time
242
+ def refresh_token
243
+ Conjur::API.authenticate_local(username, account: account, expiration: expiration, cidr: cidr).tap do
244
+ update_token_born
245
+ end
209
246
  end
210
247
  end
211
248
 
212
249
  # When the API is constructed with a token, the token cannot be refreshed.
213
250
  class UnableAuthenticator
214
- include MonotonicTime
215
-
216
251
  def refresh_token
217
252
  raise "Unable to re-authenticate using an access token"
218
253
  end
@@ -255,7 +290,7 @@ module Conjur
255
290
  end
256
291
  end
257
292
 
258
- def init_from_key username, api_key, remote_ip: nil, account: Conjur.configuration.account
293
+ def init_from_key username, api_key, account: Conjur.configuration.account, remote_ip: nil
259
294
  @username = username
260
295
  @api_key = api_key
261
296
  @remote_ip = remote_ip
@@ -276,6 +311,14 @@ module Conjur
276
311
  self
277
312
  end
278
313
 
314
+ def init_from_authn_local username, account: Conjur.configuration.account, remote_ip: nil, expiration: nil, cidr: nil
315
+ @username = username
316
+ @api_key = api_key
317
+ @remote_ip = remote_ip
318
+ @authenticator = LocalAuthenticator.new(account, username, expiration, cidr)
319
+ self
320
+ end
321
+
279
322
  attr_reader :authenticator
280
323
 
281
324
  private
@@ -26,6 +26,7 @@ module Conjur
26
26
  include QueryString
27
27
  include LogSource
28
28
  include BuildObject
29
+ include Routing
29
30
 
30
31
  attr_reader :id, :credentials
31
32
 
@@ -47,11 +48,5 @@ module Conjur
47
48
  def username
48
49
  credentials[:username] or raise "No username found in credentials"
49
50
  end
50
-
51
- protected
52
-
53
- def core_resource
54
- RestClient::Resource.new(Conjur.configuration.core_url, credentials)
55
- end
56
51
  end
57
52
  end
@@ -369,6 +369,32 @@ module Conjur
369
369
  # @see cert_file
370
370
  add_option :ssl_certificate
371
371
 
372
+ # @!attribute version
373
+ #
374
+ # Selects the major API version of the Conjur server. With this setting, the API
375
+ # will use the routing scheme for API version `4` or `5`.
376
+ #
377
+ # Methods which are not available in the selected version will raise NoMethodError.
378
+ add_option :version, default: 5
379
+
380
+ # @!attribute authn_local_socket
381
+ #
382
+ # File path to the Unix socket used for local authentication.
383
+ # This is only available when the API client is running on the Conjur server.
384
+ add_option :authn_local_socket, default: "/run/authn-local/.socket"
385
+
386
+ # Calls a major-version-specific function.
387
+ def version_logic v4_logic, v5_logic
388
+ case version.to_s
389
+ when "4"
390
+ v4_logic.call
391
+ when "5"
392
+ v5_logic.call
393
+ else
394
+ raise "Unspported major version #{version}"
395
+ end
396
+ end
397
+
372
398
  # Add the certificate configured by the {#ssl_certificate} and {#cert_file} options to the certificate
373
399
  # store used by Conjur clients.
374
400
  #
@@ -29,7 +29,13 @@ module Conjur
29
29
  # @return [Fixnum] the gidnumber
30
30
  # @raise [RestClient::Forbidden] if you don't have permission to `show` the group.
31
31
  def gidnumber
32
- annotation_value 'conjur/gidnumber'
32
+ parser_for(:group_gidnumber, group_attributes)
33
+ end
34
+
35
+ private
36
+
37
+ def group_attributes
38
+ @group_attributes ||= url_for(:group_attributes, credentials, self, id)
33
39
  end
34
40
  end
35
41
  end
@@ -23,6 +23,14 @@ module Conjur
23
23
  # methods on specific asset classes (for example, {Conjur::Resource#owner}), the are available as
24
24
  # a `Hash` on all types supporting attributes.
25
25
  module HasAttributes
26
+ class << self
27
+
28
+ # @api private
29
+ def annotation_value annotations, name
30
+ (annotations.find{|a| a['name'] == name} || {})['value']
31
+ end
32
+ end
33
+
26
34
  def as_json options={}
27
35
  result = super(options)
28
36
  if @attributes
@@ -67,7 +75,7 @@ module Conjur
67
75
  protected
68
76
 
69
77
  def annotation_value name
70
- (attributes['annotations'].find{|a| a['name'] == name} || {})['value']
78
+ HasAttributes.annotation_value attributes['annotations'], name
71
79
  end
72
80
 
73
81
  # @api private
@@ -78,9 +86,9 @@ module Conjur
78
86
 
79
87
  # @api private
80
88
  def fetch_attributes
81
- cache_key = Conjur.cache_key username, rbac_resource_resource.url
89
+ cache_key = Conjur.cache_key username, url_for(:resources_resource, credentials, id).url
82
90
  Conjur.cache.fetch_attributes cache_key do
83
- JSON.parse(rbac_resource_resource.get.body)
91
+ JSON.parse(url_for(:resources_resource, credentials, id).get.body)
84
92
  end
85
93
  end
86
94
  end
@@ -47,7 +47,7 @@ module Conjur
47
47
  options[:host_factory] = id
48
48
  options[:count] = count
49
49
  options[:cidr] = cidr if cidr
50
- response = JSON.parse core_resource['host_factory_tokens'].post(options)
50
+ response = JSON.parse url_for(:host_factory_create_tokens, credentials, id).post(options)
51
51
  response.map do |data|
52
52
  HostFactoryToken.new data, credentials
53
53
  end
@@ -0,0 +1,29 @@
1
+ module Conjur
2
+ module Routing
3
+ def url_for method, *args
4
+ router.send method, *args
5
+ end
6
+
7
+ def parser_for method, *args
8
+ router.send "parse_#{method}", *args
9
+ end
10
+
11
+ protected
12
+
13
+ def router
14
+ require 'conjur/api/router/v4'
15
+ require 'conjur/api/router/v5'
16
+
17
+ variable_id = "@v#{Conjur.configuration.version}_router"
18
+ router = instance_variable_get variable_id
19
+ if router.nil?
20
+ router = instance_variable_set variable_id, router_for_version
21
+ end
22
+ router
23
+ end
24
+
25
+ def router_for_version
26
+ Conjur::API::Router.const_get("V#{Conjur.configuration.version}")
27
+ end
28
+ end
29
+ end
@@ -28,7 +28,13 @@ module Conjur
28
28
  # @return [Fixnum] the uidnumber
29
29
  # @raise [RestClient::Forbidden] if you don't have permission to `show` the user.
30
30
  def uidnumber
31
- annotation_value 'conjur/uidnumber'
31
+ parser_for(:user_uidnumber, user_attributes)
32
+ end
33
+
34
+ private
35
+
36
+ def user_attributes
37
+ @user_attributes ||= url_for(:user_attributes, credentials, self, id)
32
38
  end
33
39
  end
34
40
  end
@@ -95,7 +95,7 @@ module Conjur
95
95
  # @note this is **not** the same as the `kind` part of a qualified Conjur id.
96
96
  # @return [String] a string representing the kind of secret.
97
97
  def kind
98
- annotation_value 'conjur/kind' || "secret"
98
+ parser_for(:variable_kind, variable_attributes) || "secret"
99
99
  end
100
100
 
101
101
  # The MIME Type of the variable's value.
@@ -109,7 +109,7 @@ module Conjur
109
109
  #
110
110
  # @return [String] a MIME type, such as `'text/plain'` or `'application/octet-stream'`.
111
111
  def mime_type
112
- annotation_value 'conjur/mime_type' || "text/plain"
112
+ parser_for(:variable_mime_type, variable_attributes) || "text/plain"
113
113
  end
114
114
 
115
115
  # Add a new value to the variable.
@@ -130,7 +130,12 @@ module Conjur
130
130
  logger << "Adding a value to variable #{id}"
131
131
  end
132
132
  invalidate do
133
- core_resource['secrets'][id.to_url_path].post value
133
+ route = url_for(:secrets_add, credentials, id)
134
+ Conjur.configuration.version_logic lambda {
135
+ route.post value: value
136
+ }, lambda {
137
+ route.post value
138
+ }
134
139
  end
135
140
  end
136
141
 
@@ -145,12 +150,16 @@ module Conjur
145
150
  #
146
151
  # @return [Integer] the number of versions
147
152
  def version_count
148
- secrets = attributes['secrets']
149
- if secrets.empty?
150
- 0
151
- else
152
- secrets.last['version']
153
- end
153
+ Conjur.configuration.version_logic lambda {
154
+ JSON.parse(url_for(:variable, credentials, id).get)['version_count']
155
+ }, lambda {
156
+ secrets = attributes['secrets']
157
+ if secrets.empty?
158
+ 0
159
+ else
160
+ secrets.last['version']
161
+ end
162
+ }
154
163
  end
155
164
 
156
165
  # Return the version of a variable.
@@ -187,7 +196,13 @@ module Conjur
187
196
  # @return [String] the value of the variable
188
197
  def value version = nil, options = {}
189
198
  options['version'] = version if version
190
- core_resource['secrets'][id.to_url_path][options_querystring options].get.body
199
+ url_for(:secrets_value, credentials, id, options).get.body
200
+ end
201
+
202
+ private
203
+
204
+ def variable_attributes
205
+ @variable_attributes ||= url_for(:variable_attributes, credentials, self, id)
191
206
  end
192
- end
207
+ end
193
208
  end
@@ -4,6 +4,8 @@ describe Conjur::HasAttributes do
4
4
  class ObjectWithAttributes
5
5
  include Conjur::HasAttributes
6
6
 
7
+ def id; "the-object"; end
8
+ def credentials; {}; end
7
9
  def username; 'alice'; end
8
10
  def url; 'http://example.com/the-object'; end
9
11
  end
@@ -18,8 +20,8 @@ describe Conjur::HasAttributes do
18
20
  let(:rbac_resource_resource) { double(:rbac_resource_resource, url: object.url) }
19
21
 
20
22
  before {
21
- allow(object).to receive(:rbac_resource_resource).and_return(rbac_resource_resource)
22
- allow(second_object).to receive(:rbac_resource_resource).and_return(rbac_resource_resource)
23
+ allow(object).to receive(:url_for).with(:resources_resource, {}, "the-object").and_return(rbac_resource_resource)
24
+ allow(second_object).to receive(:url_for).with(:resources_resource, {}, "the-object").and_return(rbac_resource_resource)
23
25
  expect(rbac_resource_resource).to receive(:get).with(no_args).and_return(double(:response, body: attributes.to_json))
24
26
  }
25
27
 
data/test.sh CHANGED
@@ -5,36 +5,50 @@ function finish {
5
5
  echo '---'
6
6
  docker-compose down --rmi 'local' --volumes
7
7
  }
8
+
8
9
  trap finish EXIT
9
10
 
10
11
  function main() {
11
12
  # Generate reports folders locally
12
- mkdir -p spec/reports features/reports
13
+ mkdir -p spec/reports features/reports features_v4/reports
13
14
 
14
15
  startConjur
15
- runTests
16
+ runTests_5
17
+ runTests_4
16
18
  }
17
19
 
18
20
  function startConjur() {
19
21
  echo 'Starting Conjur environment'
20
22
  echo '-----'
21
- docker-compose pull conjur postgres
22
- docker-compose build --pull tester
23
- docker-compose up -d conjur
23
+ docker-compose pull
24
+ docker-compose build
25
+ docker-compose up -d pg conjur_4 conjur_5
26
+ }
27
+
28
+ function runTests_5() {
29
+ echo 'Waiting for Conjur v5 to come up, and configuring it...'
30
+ ./ci/configure_v5.sh
31
+
32
+ local api_key=$(docker-compose exec -T conjur_5 rake 'role:retrieve-key[cucumber:user:admin]')
33
+
34
+ echo 'Running tests'
35
+ echo '-----'
36
+ docker-compose run --rm \
37
+ -e CONJUR_AUTHN_API_KEY="$api_key" \
38
+ tester_5 rake jenkins_init jenkins_spec jenkins_cucumber_v5
24
39
  }
25
40
 
26
- function runTests() {
27
- echo 'waiting for Conjur to come up...'
28
- # TODO: remove this once we have HEALTHCHECK in place
29
- docker-compose run --rm tester ./ci/wait_for_server.sh
41
+ function runTests_4() {
42
+ echo 'Waiting for Conjur v4 to come up, and configuring it...'
43
+ ./ci/configure_v4.sh
30
44
 
31
- local api_key=$(docker-compose exec -T conjur rails r "print Credentials['cucumber:user:admin'].api_key")
45
+ local api_key=$(docker-compose exec -T conjur_4 su conjur -c "conjur-plugin-service authn env RAILS_ENV=appliance rails r \"puts User['admin'].api_key\" 2>/dev/null")
32
46
 
33
47
  echo 'Running tests'
34
48
  echo '-----'
35
49
  docker-compose run --rm \
36
50
  -e CONJUR_AUTHN_API_KEY="$api_key" \
37
- tester
51
+ tester_4 rake jenkins_cucumber_v4
38
52
  }
39
53
 
40
54
  main