conjur-api 5.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
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