conjur-api 6.3.0 → 6.3.1.pre.819

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: a6cd769a0f1013ecd2748b1ad9bd8f57805ae032706fcefc1d5b335aac296761
4
- data.tar.gz: 48dedbb4d1e1543a4e7c61233803db4765ca06971470c5a4d81f2bb99b9aab7a
3
+ metadata.gz: 530b8d1955af810bdfc511cf8b2cde690dfb6a4afc4a7a2505b86a2aa92ece1d
4
+ data.tar.gz: 6f3ce0a8e7780eef3cfd8d55d174fadcd06593539cae63146bac3ec796cbe510
5
5
  SHA512:
6
- metadata.gz: 0f3d542b1d470e77a52c729d7a9bcf43c6917599494d431e9f3da1c464413cc21699f52a4d354c74c576b9caf478c807a42cbcca4bf07acd9e87df580d7ab1a9
7
- data.tar.gz: 7046f9809eb78899875493c2114a487b7586679e0a617ad85189fc601a38bd07c8ded050cc0d541061fb5e8d39439b0f2883c8758891383f7de552313a3e2d37
6
+ metadata.gz: 704610d1d7046bbf65619078c5273b1c4680969fa81659575d976fe42b1cc14f4d23d8b0933b0daeb09549b466df13414b166e96860e0fd74bd8a0dac20d906e
7
+ data.tar.gz: 7391aa9ccdd2f31b38180c8a5d6c78cd7958519af25fae415682e91ecab8d5834d02bd62931cd7fa4728b5665b19ca265f8e1ef88b762a1dbd64b8c983480391
data/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## [6.3.1] - 2026-07-16
10
+
11
+ ### Added
12
+ - Added `Conjur::API#dry_run_policy` for validating a policy load without applying it,
13
+ reporting what would be created/updated/deleted or, for invalid YAML, the parse errors.
14
+
15
+ ### Changed
16
+ - `Conjur::API#dry_run_policy` and `Conjur::API#fetch_policy` now raise
17
+ `Conjur::FeatureNotAvailable` when called against CyberArk Secrets Manager, SaaS, or
18
+ against a Conjur server older than 1.21.1, matching conjur-api-go's behavior for the
19
+ equivalent methods.
20
+
9
21
  ## [6.3.0] - 2026-07-15
10
22
 
11
23
  ### Added
data/VERSION CHANGED
@@ -1 +1 @@
1
- 6.3.0
1
+ 6.3.1-819
@@ -92,3 +92,40 @@ Feature: Load a policy.
92
92
  $conjur.fetch_policy 'fetch-policy-json', return_json: true
93
93
  """
94
94
  Then the result should contain "fetch-policy-json-group"
95
+
96
+ Scenario: A dry run reports what a policy load would do without applying it.
97
+ Given I run the code:
98
+ """
99
+ $conjur.load_policy 'root', <<-POLICY
100
+ - !policy
101
+ id: dry-run-policy
102
+ body: []
103
+ POLICY
104
+ """
105
+ Then I can run the code:
106
+ """
107
+ $conjur.dry_run_policy 'dry-run-policy', <<-POLICY
108
+ - !group dry-run-group
109
+ POLICY
110
+ """
111
+ Then the JSON should have "status"
112
+ Then I can run the code:
113
+ """
114
+ $conjur.fetch_policy 'dry-run-policy'
115
+ """
116
+ Then the result should not contain "dry-run-group"
117
+
118
+ Scenario: A dry run reports errors for invalid policy YAML without applying it.
119
+ Given I run the code:
120
+ """
121
+ $conjur.load_policy 'root', <<-POLICY
122
+ - !policy
123
+ id: dry-run-invalid-policy
124
+ body: []
125
+ POLICY
126
+ """
127
+ Then I can run the code:
128
+ """
129
+ $conjur.dry_run_policy 'dry-run-invalid-policy', "- !group [invalid"
130
+ """
131
+ Then the JSON should have "errors"
@@ -6,6 +6,10 @@ Then(/^the result should contain "([^"]+)"$/) do |expected|
6
6
  expect(@result.to_s).to include(expected)
7
7
  end
8
8
 
9
+ Then(/^the result should not contain "([^"]+)"$/) do |expected|
10
+ expect(@result.to_s).not_to include(expected)
11
+ end
12
+
9
13
  Then(/^the providers list contains service id "([^"]+)"$/) do |service_id|
10
14
  expect(@result.map{ |x| x["service_id"]}).to include(service_id)
11
15
  end
@@ -20,6 +20,7 @@
20
20
  #
21
21
  require 'conjur/policy_load_result'
22
22
  require 'conjur/policy'
23
+ require 'conjur/saas'
23
24
 
24
25
  module Conjur
25
26
  class API
@@ -51,6 +52,29 @@ module Conjur
51
52
  PolicyLoadResult.new JSON.parse(request.send(method, policy))
52
53
  end
53
54
 
55
+ # Validate a policy load without applying it to the server, reporting what
56
+ # would change.
57
+ #
58
+ # @param id [String] id of the policy to load.
59
+ # @param policy [String] YAML-formatted policy definition.
60
+ # @param account [String] Conjur organization account
61
+ # @param method [Symbol] Policy load method to use: {POLICY_METHOD_POST} (default), {POLICY_METHOD_PATCH}, or {POLICY_METHOD_PUT}.
62
+ # @return [Hash] the dry run result, with keys such as "status", "created", "updated",
63
+ # "deleted", and "errors". Invalid policy YAML is reported in the result rather than
64
+ # raised, with "status" set to "Invalid YAML" and details in "errors". This applies
65
+ # only to the 422 response the server uses for invalid YAML; other error responses
66
+ # (e.g. 403 for insufficient privileges, 400 for bad request params) have a different
67
+ # body shape and are raised as the corresponding RestClient exception.
68
+ # @raise [Conjur::FeatureNotAvailable] if the appliance is CyberArk Secrets Manager, SaaS,
69
+ # or the Conjur server is older than 1.21.1.
70
+ def dry_run_policy id, policy, account: Conjur.configuration.account, method: POLICY_METHOD_POST
71
+ verify_policy_dry_run_support!
72
+ request = url_for(:policies_dry_run_policy, credentials, account, id)
73
+ JSON.parse(request.send(method, policy))
74
+ rescue RestClient::UnprocessableEntity => e
75
+ JSON.parse(e.response.body)
76
+ end
77
+
54
78
  # Fetch the current policy data from the server.
55
79
  #
56
80
  # @param id [String] id of the policy to fetch.
@@ -59,7 +83,10 @@ module Conjur
59
83
  # @param depth [Integer, nil] Maximum depth of the returned policy tree (nil for the full tree).
60
84
  # @param limit [Integer, nil] Maximum number of policy objects to return (nil for no limit).
61
85
  # @return [String] the policy document, formatted as YAML or JSON.
86
+ # @raise [Conjur::FeatureNotAvailable] if the appliance is CyberArk Secrets Manager, SaaS,
87
+ # or the Conjur server is older than 1.21.1.
62
88
  def fetch_policy id, account: Conjur.configuration.account, return_json: false, depth: nil, limit: nil
89
+ verify_policy_dry_run_support!
63
90
  options = {}
64
91
  options[:depth] = depth if depth
65
92
  options[:limit] = limit if limit
@@ -69,5 +96,15 @@ module Conjur
69
96
  end
70
97
 
71
98
  #@!endgroup
99
+
100
+ private
101
+
102
+ def verify_policy_dry_run_support!
103
+ if Conjur::Saas.appliance_url?(Conjur.configuration.appliance_url)
104
+ raise Conjur::FeatureNotAvailable, "Policy dry run and fetch are not supported in CyberArk Secrets Manager, SaaS"
105
+ end
106
+
107
+ verify_min_server_version!('1.21.1')
108
+ end
72
109
  end
73
110
  end
@@ -148,6 +148,13 @@ module Conjur
148
148
  )['policies'][fully_escape account]['policy'][fully_escape id]
149
149
  end
150
150
 
151
+ def policies_dry_run_policy credentials, account, id
152
+ RestClient::Resource.new(
153
+ Conjur.configuration.core_url,
154
+ Conjur.configuration.create_rest_client_options(credentials)
155
+ )['policies'][fully_escape account]['policy'][fully_escape id]['?dryRun=true']
156
+ end
157
+
151
158
  def policies_fetch_policy credentials, account, id, options = {}
152
159
  RestClient::Resource.new(
153
160
  Conjur.configuration.core_url,
@@ -275,6 +282,20 @@ module Conjur
275
282
  )['whoami']
276
283
  end
277
284
 
285
+ def server_info
286
+ RestClient::Resource.new(
287
+ Conjur.configuration.core_url,
288
+ Conjur.configuration.rest_client_options
289
+ )['info']
290
+ end
291
+
292
+ def server_root
293
+ RestClient::Resource.new(
294
+ Conjur.configuration.core_url,
295
+ Conjur.configuration.rest_client_options
296
+ )['/']
297
+ end
298
+
278
299
  private
279
300
 
280
301
  def resource_annotations resource
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+ require 'conjur/api/router'
3
+
4
+ module Conjur
5
+ class API
6
+ #@!group Server version
7
+
8
+ # Retrieve the Conjur server version.
9
+ #
10
+ # Queries the '/info' endpoint (available on Conjur Enterprise), falling back to
11
+ # the root endpoint (available on Conjur OSS, and as a legacy fallback on
12
+ # Enterprise) if '/info' is unavailable or its response can't be parsed. The
13
+ # result is memoized on this instance.
14
+ #
15
+ # @return [String] the server version, e.g. "1.21.1.1-25".
16
+ # @raise [Conjur::FeatureNotAvailable] if the version can't be determined from
17
+ # either endpoint.
18
+ def server_version
19
+ return @server_version if @server_version
20
+
21
+ @server_version = version_from_info || version_from_root ||
22
+ raise(Conjur::FeatureNotAvailable, "Unable to determine Conjur server version")
23
+ end
24
+
25
+ #@!endgroup
26
+
27
+ private
28
+
29
+ def version_from_info
30
+ body = JSON.parse(Router.server_info.get.body)
31
+ body.dig('services', 'possum', 'version')
32
+ rescue RestClient::Exception, JSON::ParserError
33
+ nil
34
+ end
35
+
36
+ def version_from_root
37
+ response = Router.server_root.get
38
+ content_type = response.headers[:content_type].to_s
39
+
40
+ if content_type.include?('application/json')
41
+ JSON.parse(response.body)['version']
42
+ else
43
+ match = response.body.match(%r{<d[dt]>\s*Version\s*(?:</d[dt]>\s*<dd>\s*)?([^\s<]+)\s*</dd>})
44
+ match && match[1]
45
+ end
46
+ rescue RestClient::Exception, JSON::ParserError
47
+ nil
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,34 @@
1
+ module Conjur
2
+ class API
3
+ #@!group Server version
4
+
5
+ # Verify that the Conjur server version is at least `min_version`.
6
+ #
7
+ # Conjur versions carry a build suffix after a hyphen (e.g. "1.21.1.1-25")
8
+ # which is not part of the comparable version, so it's stripped before comparing.
9
+ #
10
+ # @param min_version [String] the minimum required version, e.g. "1.21.1".
11
+ # @return [true] if the server version is sufficient.
12
+ # @raise [Conjur::FeatureNotAvailable] if the server version is unparseable or
13
+ # lower than `min_version`.
14
+ def verify_min_server_version!(min_version)
15
+ detected = server_version.split('-').first
16
+
17
+ begin
18
+ detected_version = Gem::Version.new(detected)
19
+ required_version = Gem::Version.new(min_version)
20
+ rescue ArgumentError
21
+ raise Conjur::FeatureNotAvailable, "Unable to parse Conjur server version #{server_version.inspect}"
22
+ end
23
+
24
+ if detected_version < required_version
25
+ raise Conjur::FeatureNotAvailable,
26
+ "Conjur server version #{server_version} is less than the minimum required version #{min_version}"
27
+ end
28
+
29
+ true
30
+ end
31
+
32
+ #@!endgroup
33
+ end
34
+ end
data/lib/conjur/api.rb CHANGED
@@ -42,6 +42,8 @@ require 'conjur/api/resources'
42
42
  require 'conjur/api/pubkeys'
43
43
  require 'conjur/api/variables'
44
44
  require 'conjur/api/policies'
45
+ require 'conjur/api/server_version'
46
+ require 'conjur/api/version_check'
45
47
  require 'conjur/api/host_factories'
46
48
  require 'conjur/api/ldap_sync'
47
49
  require 'conjur/host'
@@ -0,0 +1,39 @@
1
+ require 'uri'
2
+
3
+ module Conjur
4
+ # Detects whether a Conjur appliance URL points at CyberArk Secrets Manager, SaaS.
5
+ # SaaS appliances do not expose the '/info' or root version endpoints used to
6
+ # determine server version, so features that depend on those endpoints must be
7
+ # rejected outright for SaaS rather than attempting a version check.
8
+ module Saas
9
+ # Hostname suffixes used by CyberArk Secrets Manager, SaaS appliances.
10
+ SUFFIXES = %w[
11
+ .cyberark.cloud
12
+ .integration-cyberark.cloud
13
+ .test-cyberark.cloud
14
+ .dev-cyberark.cloud
15
+ .cyberark-everest-integdev.cloud
16
+ .cyberark-everest-pre-prod.cloud
17
+ .sandbox-cyberark.cloud
18
+ .pt-cyberark.cloud
19
+ ].freeze
20
+
21
+ HOSTNAME_PATTERN = /(\.secretsmgr|-secretsmanager)(#{SUFFIXES.map { |s| Regexp.escape(s) }.join('|')})\z/.freeze
22
+
23
+ # Returns true if `url` is a CyberArk Secrets Manager, SaaS appliance URL.
24
+ #
25
+ # @param url [String, nil] the appliance URL to check.
26
+ # @return [Boolean]
27
+ def self.appliance_url?(url)
28
+ return false unless url
29
+
30
+ uri = URI.parse(url)
31
+ return false unless uri.scheme == 'https'
32
+ return false unless uri.host
33
+
34
+ !!(uri.host.downcase =~ HOSTNAME_PATTERN)
35
+ rescue URI::InvalidURIError, ArgumentError
36
+ false
37
+ end
38
+ end
39
+ end
@@ -10,7 +10,23 @@ describe "Conjur::API#fetch_policy", api: :dummy do
10
10
  allow(api).to receive(:credentials).and_return('the-credentials')
11
11
  end
12
12
 
13
+ it "raises FeatureNotAvailable for a SaaS appliance" do
14
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://x.secretsmgr.cyberark.cloud')
15
+
16
+ expect { api.fetch_policy('root', account: 'the-account') }.to raise_error(Conjur::FeatureNotAvailable, /SaaS/)
17
+ end
18
+
19
+ it "raises FeatureNotAvailable for a server older than 1.21.1" do
20
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
21
+ allow(api).to receive(:server_version).and_return('1.20.0')
22
+
23
+ expect { api.fetch_policy('root', account: 'the-account') }.to raise_error(Conjur::FeatureNotAvailable, /1\.21\.1/)
24
+ end
25
+
13
26
  it "fetches the policy as YAML by default" do
27
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
28
+ allow(api).to receive(:server_version).and_return('1.21.1')
29
+
14
30
  resource = instance_double(RestClient::Resource, "policy resource")
15
31
  allow(api).to receive(:url_for)
16
32
  .with(:policies_fetch_policy, 'the-credentials', 'the-account', 'root', {})
@@ -24,6 +40,9 @@ describe "Conjur::API#fetch_policy", api: :dummy do
24
40
  end
25
41
 
26
42
  it "fetches the policy as JSON when requested" do
43
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
44
+ allow(api).to receive(:server_version).and_return('1.21.1')
45
+
27
46
  resource = instance_double(RestClient::Resource, "policy resource")
28
47
  allow(api).to receive(:url_for)
29
48
  .with(:policies_fetch_policy, 'the-credentials', 'the-account', 'root', { depth: 2, limit: 100 })
@@ -38,3 +57,102 @@ describe "Conjur::API#fetch_policy", api: :dummy do
38
57
  ).to eq('[{"id":"cucumber:host:foo"}]')
39
58
  end
40
59
  end
60
+
61
+ describe "Conjur::API#dry_run_policy", api: :dummy do
62
+ let(:api) { Conjur::API.new_from_key('user', 'pass') }
63
+
64
+ before do
65
+ allow(api).to receive(:credentials).and_return('the-credentials')
66
+ end
67
+
68
+ it "raises FeatureNotAvailable for a SaaS appliance" do
69
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://x.secretsmgr.cyberark.cloud')
70
+
71
+ expect { api.dry_run_policy('root', 'policy body', account: 'the-account') }.to raise_error(Conjur::FeatureNotAvailable, /SaaS/)
72
+ end
73
+
74
+ it "raises FeatureNotAvailable for a server older than 1.21.1" do
75
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
76
+ allow(api).to receive(:server_version).and_return('1.20.0')
77
+
78
+ expect { api.dry_run_policy('root', 'policy body', account: 'the-account') }.to raise_error(Conjur::FeatureNotAvailable, /1\.21\.1/)
79
+ end
80
+
81
+ it "validates a policy load without applying it, defaulting to POST" do
82
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
83
+ allow(api).to receive(:server_version).and_return('1.21.1')
84
+
85
+ resource = instance_double(RestClient::Resource, "policy resource")
86
+ allow(api).to receive(:url_for)
87
+ .with(:policies_dry_run_policy, 'the-credentials', 'the-account', 'root')
88
+ .and_return(resource)
89
+
90
+ allow(resource).to receive(:post).with('policy body').and_return(
91
+ '{"status":"Valid YAML","created":{"items":[]},"updated":{"before":{"items":[]},"after":{"items":[]}},"deleted":{"items":[]}}'
92
+ )
93
+
94
+ result = api.dry_run_policy('root', 'policy body', account: 'the-account')
95
+
96
+ expect(result['status']).to eq('Valid YAML')
97
+ end
98
+
99
+ it "supports POLICY_METHOD_PUT" do
100
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
101
+ allow(api).to receive(:server_version).and_return('1.21.1')
102
+
103
+ resource = instance_double(RestClient::Resource, "policy resource")
104
+ allow(api).to receive(:url_for)
105
+ .with(:policies_dry_run_policy, 'the-credentials', 'the-account', 'root')
106
+ .and_return(resource)
107
+
108
+ allow(resource).to receive(:put).with('policy body').and_return(
109
+ '{"status":"Valid YAML"}'
110
+ )
111
+
112
+ result = api.dry_run_policy(
113
+ 'root', 'policy body', account: 'the-account', method: Conjur::API::POLICY_METHOD_PUT
114
+ )
115
+
116
+ expect(result['status']).to eq('Valid YAML')
117
+ end
118
+
119
+ it "reports invalid policy YAML instead of raising" do
120
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
121
+ allow(api).to receive(:server_version).and_return('1.21.1')
122
+
123
+ resource = instance_double(RestClient::Resource, "policy resource")
124
+ allow(api).to receive(:url_for)
125
+ .with(:policies_dry_run_policy, 'the-credentials', 'the-account', 'root')
126
+ .and_return(resource)
127
+
128
+ response_body = '{"status":"Invalid YAML","errors":[{"line":1,"column":10,"message":"bad yaml"}]}'
129
+ response = instance_double(RestClient::Response, "error response", body: response_body, code: 422)
130
+ exception = RestClient::UnprocessableEntity.new(response)
131
+ allow(exception).to receive(:response).and_return(response)
132
+ allow(resource).to receive(:post).with('- !group [invalid').and_raise(exception)
133
+
134
+ result = api.dry_run_policy('root', '- !group [invalid', account: 'the-account')
135
+
136
+ expect(result['status']).to eq('Invalid YAML')
137
+ expect(result['errors'].first['message']).to eq('bad yaml')
138
+ end
139
+
140
+ it "raises for other error responses instead of returning them as a result" do
141
+ allow(Conjur.configuration).to receive(:appliance_url).and_return('https://conjur.example.com')
142
+ allow(api).to receive(:server_version).and_return('1.21.1')
143
+
144
+ resource = instance_double(RestClient::Resource, "policy resource")
145
+ allow(api).to receive(:url_for)
146
+ .with(:policies_dry_run_policy, 'the-credentials', 'the-account', 'root')
147
+ .and_return(resource)
148
+
149
+ response = instance_double(RestClient::Response, "error response", body: '{"error":"forbidden"}', code: 403)
150
+ exception = RestClient::Forbidden.new(response)
151
+ allow(exception).to receive(:response).and_return(response)
152
+ allow(resource).to receive(:post).with('policy body').and_raise(exception)
153
+
154
+ expect {
155
+ api.dry_run_policy('root', 'policy body', account: 'the-account')
156
+ }.to raise_error(RestClient::Forbidden)
157
+ end
158
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'conjur/api/router'
5
+
6
+ describe Conjur::API::Router do
7
+ before do
8
+ allow(Conjur.configuration).to receive(:core_url).and_return('http://core.example.com')
9
+ end
10
+
11
+ describe ".server_info" do
12
+ it "builds an unauthenticated resource for the info endpoint" do
13
+ resource = Conjur::API::Router.server_info
14
+
15
+ expect(resource.url).to eq('http://core.example.com/info')
16
+ expect(resource.options[:headers]).not_to have_key(:authorization)
17
+ end
18
+ end
19
+
20
+ describe ".server_root" do
21
+ it "builds an unauthenticated resource for the root endpoint" do
22
+ resource = Conjur::API::Router.server_root
23
+
24
+ expect(resource.url).to eq('http://core.example.com/')
25
+ end
26
+ end
27
+
28
+ describe ".policies_load_policy" do
29
+ it "builds a resource with no trailing slash or query string" do
30
+ resource = Conjur::API::Router.policies_load_policy({}, 'the-account', 'root')
31
+
32
+ expect(resource.url).to eq('http://core.example.com/policies/the-account/policy/root')
33
+ end
34
+ end
35
+
36
+ describe ".policies_dry_run_policy" do
37
+ it "builds a resource with the dryRun query string" do
38
+ resource = Conjur::API::Router.policies_dry_run_policy({}, 'the-account', 'root')
39
+
40
+ expect(resource.url).to eq('http://core.example.com/policies/the-account/policy/root/?dryRun=true')
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'conjur/api/server_version'
5
+
6
+ describe "Conjur::API#server_version", api: :dummy do
7
+ let(:info_resource) { instance_double(RestClient::Resource, "info resource") }
8
+ let(:root_resource) { instance_double(RestClient::Resource, "root resource") }
9
+
10
+ before do
11
+ allow(Conjur::API::Router).to receive(:server_info).and_return(info_resource)
12
+ allow(Conjur::API::Router).to receive(:server_root).and_return(root_resource)
13
+ end
14
+
15
+ it "returns the possum service version from the /info endpoint" do
16
+ body = {
17
+ "services" => { "possum" => { "version" => "1.21.1.1-25" } }
18
+ }.to_json
19
+ allow(info_resource).to receive(:get).and_return(
20
+ instance_double(RestClient::Response, "info response", body: body)
21
+ )
22
+
23
+ expect(api.server_version).to eq("1.21.1.1-25")
24
+ end
25
+
26
+ it "falls back to root when /info responds 404 (OSS)" do
27
+ response = instance_double(RestClient::Response, "404 response", body: '', code: 404)
28
+ allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(response))
29
+
30
+ root_body = { "version" => "1.20.0" }.to_json
31
+ allow(root_resource).to receive(:get).and_return(
32
+ instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'application/json' })
33
+ )
34
+
35
+ expect(api.server_version).to eq("1.20.0")
36
+ end
37
+
38
+ it "falls back to root when /info responds 500 (not just 404/401)" do
39
+ response = instance_double(RestClient::Response, "500 response", body: '', code: 500)
40
+ allow(info_resource).to receive(:get).and_raise(RestClient::InternalServerError.new(response))
41
+
42
+ root_body = { "version" => "1.20.0" }.to_json
43
+ allow(root_resource).to receive(:get).and_return(
44
+ instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'application/json' })
45
+ )
46
+
47
+ expect(api.server_version).to eq("1.20.0")
48
+ end
49
+
50
+ it "parses a JSON root response even when Content-Type includes a charset" do
51
+ allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
52
+
53
+ root_body = { "version" => "1.20.0" }.to_json
54
+ allow(root_resource).to receive(:get).and_return(
55
+ instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'application/json; charset=utf-8' })
56
+ )
57
+
58
+ expect(api.server_version).to eq("1.20.0")
59
+ end
60
+
61
+ it "parses an HTML root response when Content-Type is not JSON" do
62
+ allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
63
+
64
+ root_body = "<html><body><dd>Version 1.19.0-359</dd></body></html>"
65
+ allow(root_resource).to receive(:get).and_return(
66
+ instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'text/html' })
67
+ )
68
+
69
+ expect(api.server_version).to eq("1.19.0-359")
70
+ end
71
+
72
+ it "parses an HTML root response with the Version label and value in separate tags" do
73
+ allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
74
+
75
+ root_body = "<html><body><dt>Version</dt><dd>1.28.0-1443</dd></body></html>"
76
+ allow(root_resource).to receive(:get).and_return(
77
+ instance_double(RestClient::Response, "root response", body: root_body, headers: { content_type: 'text/html' })
78
+ )
79
+
80
+ expect(api.server_version).to eq("1.28.0-1443")
81
+ end
82
+
83
+ it "raises FeatureNotAvailable when neither endpoint yields a version" do
84
+ allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
85
+ allow(root_resource).to receive(:get).and_raise(RestClient::InternalServerError.new(instance_double(RestClient::Response, body: '', code: 500)))
86
+
87
+ expect { api.server_version }.to raise_error(Conjur::FeatureNotAvailable, /Unable to determine Conjur server version/)
88
+ end
89
+
90
+ it "memoizes a successful lookup" do
91
+ body = { "services" => { "possum" => { "version" => "1.21.1" } } }.to_json
92
+ allow(info_resource).to receive(:get).and_return(
93
+ instance_double(RestClient::Response, "info response", body: body)
94
+ )
95
+
96
+ api.server_version
97
+ api.server_version
98
+
99
+ expect(info_resource).to have_received(:get).once
100
+ end
101
+
102
+ it "does not memoize a failed lookup, retrying on the next call" do
103
+ allow(info_resource).to receive(:get).and_raise(RestClient::NotFound.new(instance_double(RestClient::Response, body: '', code: 404)))
104
+ allow(root_resource).to receive(:get).and_raise(RestClient::InternalServerError.new(instance_double(RestClient::Response, body: '', code: 500)))
105
+
106
+ expect { api.server_version }.to raise_error(Conjur::FeatureNotAvailable)
107
+ expect { api.server_version }.to raise_error(Conjur::FeatureNotAvailable)
108
+
109
+ expect(info_resource).to have_received(:get).twice
110
+ end
111
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'conjur/api/version_check'
5
+
6
+ describe "Conjur::API#verify_min_server_version!", api: :dummy do
7
+ it "passes when the server version equals the minimum" do
8
+ allow(api).to receive(:server_version).and_return("1.21.1")
9
+
10
+ expect(api.verify_min_server_version!("1.21.1")).to be true
11
+ end
12
+
13
+ it "passes when the server version is greater than the minimum" do
14
+ allow(api).to receive(:server_version).and_return("1.22.0")
15
+
16
+ expect(api.verify_min_server_version!("1.21.1")).to be true
17
+ end
18
+
19
+ it "passes when a Conjur build suffix is present and the base version is sufficient" do
20
+ allow(api).to receive(:server_version).and_return("1.21.1.1-25")
21
+
22
+ expect(api.verify_min_server_version!("1.21.1")).to be true
23
+ end
24
+
25
+ it "raises when the server version is less than the minimum" do
26
+ allow(api).to receive(:server_version).and_return("1.20.0")
27
+
28
+ expect { api.verify_min_server_version!("1.21.1") }.to raise_error(
29
+ Conjur::FeatureNotAvailable, /1\.20\.0.*1\.21\.1|1\.21\.1.*1\.20\.0/
30
+ )
31
+ end
32
+
33
+ it "raises FeatureNotAvailable (not ArgumentError) for an unparseable server version" do
34
+ allow(api).to receive(:server_version).and_return("development")
35
+
36
+ expect { api.verify_min_server_version!("1.21.1") }.to raise_error(
37
+ Conjur::FeatureNotAvailable, /Unable to parse Conjur server version/
38
+ )
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'conjur/saas'
5
+
6
+ describe Conjur::Saas do
7
+ describe ".appliance_url?" do
8
+ it "matches a .secretsmgr SaaS hostname" do
9
+ expect(Conjur::Saas.appliance_url?("https://myorg.secretsmgr.cyberark.cloud")).to be true
10
+ end
11
+
12
+ it "matches a -secretsmanager SaaS hostname" do
13
+ expect(Conjur::Saas.appliance_url?("https://myorg-secretsmanager.dev-cyberark.cloud")).to be true
14
+ end
15
+
16
+ it "matches with additional subdomains before the prefix" do
17
+ expect(Conjur::Saas.appliance_url?("https://tenant.myorg.secretsmgr.sandbox-cyberark.cloud")).to be true
18
+ end
19
+
20
+ it "does not match a plain appliance URL" do
21
+ expect(Conjur::Saas.appliance_url?("https://conjur.example.com/api")).to be false
22
+ end
23
+
24
+ it "does not match when the scheme is not https" do
25
+ expect(Conjur::Saas.appliance_url?("http://myorg.secretsmgr.cyberark.cloud")).to be false
26
+ end
27
+
28
+ it "does not match a nil url" do
29
+ expect(Conjur::Saas.appliance_url?(nil)).to be false
30
+ end
31
+
32
+ it "does not raise for a malformed url and returns false" do
33
+ expect(Conjur::Saas.appliance_url?("not a url with spaces")).to be false
34
+ end
35
+
36
+ it "does not match a relative or opaque url with no host" do
37
+ expect(Conjur::Saas.appliance_url?("mailto:someone@example.com")).to be false
38
+ end
39
+ end
40
+ 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: 6.3.0
4
+ version: 6.3.1.pre.819
5
5
  platform: ruby
6
6
  authors:
7
7
  - CyberArk Maintainers
@@ -350,7 +350,9 @@ files:
350
350
  - lib/conjur/api/resources.rb
351
351
  - lib/conjur/api/roles.rb
352
352
  - lib/conjur/api/router.rb
353
+ - lib/conjur/api/server_version.rb
353
354
  - lib/conjur/api/variables.rb
355
+ - lib/conjur/api/version_check.rb
354
356
  - lib/conjur/base.rb
355
357
  - lib/conjur/base_object.rb
356
358
  - lib/conjur/build_object.rb
@@ -378,6 +380,7 @@ files:
378
380
  - lib/conjur/role.rb
379
381
  - lib/conjur/role_grant.rb
380
382
  - lib/conjur/routing.rb
383
+ - lib/conjur/saas.rb
381
384
  - lib/conjur/user.rb
382
385
  - lib/conjur/variable.rb
383
386
  - lib/conjur/webservice.rb
@@ -385,6 +388,9 @@ files:
385
388
  - spec/.conjurrc
386
389
  - spec/api/host_factories_spec.rb
387
390
  - spec/api/policies_spec.rb
391
+ - spec/api/router_spec.rb
392
+ - spec/api/server_version_spec.rb
393
+ - spec/api/version_check_spec.rb
388
394
  - spec/api_spec.rb
389
395
  - spec/authn_cert_spec.rb
390
396
  - spec/base_object_spec.rb
@@ -405,6 +411,7 @@ files:
405
411
  - spec/spec_helper.rb
406
412
  - spec/ssl_spec.rb
407
413
  - spec/unit/policy_load_result_spec.rb
414
+ - spec/unit/saas_spec.rb
408
415
  - spec/uri_escape_spec.rb
409
416
  - test.sh
410
417
  - tmp/.keep
@@ -460,6 +467,9 @@ test_files:
460
467
  - spec/.conjurrc
461
468
  - spec/api/host_factories_spec.rb
462
469
  - spec/api/policies_spec.rb
470
+ - spec/api/router_spec.rb
471
+ - spec/api/server_version_spec.rb
472
+ - spec/api/version_check_spec.rb
463
473
  - spec/api_spec.rb
464
474
  - spec/authn_cert_spec.rb
465
475
  - spec/base_object_spec.rb
@@ -480,4 +490,5 @@ test_files:
480
490
  - spec/spec_helper.rb
481
491
  - spec/ssl_spec.rb
482
492
  - spec/unit/policy_load_result_spec.rb
493
+ - spec/unit/saas_spec.rb
483
494
  - spec/uri_escape_spec.rb